michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim:set expandtab ts=4 sw=4 sts=4 cin: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // HttpLog.h should generally be included first michael@0: #include "HttpLog.h" michael@0: michael@0: #include "nsHttp.h" michael@0: #include "nsHttpChannel.h" michael@0: #include "nsHttpHandler.h" michael@0: #include "nsIApplicationCacheService.h" michael@0: #include "nsIApplicationCacheContainer.h" michael@0: #include "nsICacheStorageService.h" michael@0: #include "nsICacheStorage.h" michael@0: #include "nsICacheEntry.h" michael@0: #include "nsICryptoHash.h" michael@0: #include "nsIStringBundle.h" michael@0: #include "nsIStreamListenerTee.h" michael@0: #include "nsISeekableStream.h" michael@0: #include "nsILoadGroupChild.h" michael@0: #include "nsIProtocolProxyService2.h" michael@0: #include "nsMimeTypes.h" michael@0: #include "nsNetUtil.h" michael@0: #include "prprf.h" michael@0: #include "prnetdb.h" michael@0: #include "nsEscape.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "nsIOService.h" michael@0: #include "nsDNSPrefetch.h" michael@0: #include "nsChannelClassifier.h" michael@0: #include "nsIRedirectResultListener.h" michael@0: #include "mozilla/TimeStamp.h" michael@0: #include "nsError.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsAlgorithm.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "nsIConsoleService.h" michael@0: #include "mozilla/Attributes.h" michael@0: #include "mozilla/VisualEventTracer.h" michael@0: #include "nsISSLSocketControl.h" michael@0: #include "sslt.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIPermissionManager.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsISSLStatus.h" michael@0: #include "nsISSLStatusProvider.h" michael@0: #include "LoadContextInfo.h" michael@0: #include "netCore.h" michael@0: #include "nsHttpTransaction.h" michael@0: #include "nsICacheEntryDescriptor.h" michael@0: #include "nsICancelable.h" michael@0: #include "nsIHttpChannelAuthProvider.h" michael@0: #include "nsIHttpEventSink.h" michael@0: #include "nsIPrompt.h" michael@0: #include "nsInputStreamPump.h" michael@0: #include "nsURLHelper.h" michael@0: #include "nsISocketTransport.h" michael@0: #include "nsICacheSession.h" michael@0: #include "nsIStreamConverterService.h" michael@0: #include "nsISiteSecurityService.h" michael@0: #include "nsCRT.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsPerformance.h" michael@0: #include "CacheObserver.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozIThirdPartyUtil.h" michael@0: michael@0: namespace mozilla { namespace net { michael@0: michael@0: namespace { michael@0: michael@0: // True if the local cache should be bypassed when processing a request. michael@0: #define BYPASS_LOCAL_CACHE(loadFlags) \ michael@0: (loadFlags & (nsIRequest::LOAD_BYPASS_CACHE | \ michael@0: nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE)) michael@0: michael@0: #define CACHE_FILE_GONE(result) \ michael@0: ((result) == NS_ERROR_FILE_NOT_FOUND || \ michael@0: (result) == NS_ERROR_FILE_CORRUPTED) michael@0: michael@0: static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID); michael@0: static NS_DEFINE_CID(kStreamTransportServiceCID, michael@0: NS_STREAMTRANSPORTSERVICE_CID); michael@0: michael@0: enum CacheDisposition { michael@0: kCacheHit = 1, michael@0: kCacheHitViaReval = 2, michael@0: kCacheMissedViaReval = 3, michael@0: kCacheMissed = 4 michael@0: }; michael@0: michael@0: void michael@0: AccumulateCacheHitTelemetry(CacheDisposition hitOrMiss) michael@0: { michael@0: if (!CacheObserver::UseNewCache()) { michael@0: Telemetry::Accumulate(Telemetry::HTTP_CACHE_DISPOSITION_2, hitOrMiss); michael@0: } michael@0: else { michael@0: Telemetry::Accumulate(Telemetry::HTTP_CACHE_DISPOSITION_2_V2, hitOrMiss); michael@0: michael@0: int32_t experiment = CacheObserver::HalfLifeExperiment(); michael@0: if (experiment > 0 && hitOrMiss == kCacheMissed) { michael@0: Telemetry::Accumulate(Telemetry::HTTP_CACHE_MISS_HALFLIFE_EXPERIMENT, michael@0: experiment - 1); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Computes and returns a SHA1 hash of the input buffer. The input buffer michael@0: // must be a null-terminated string. michael@0: nsresult michael@0: Hash(const char *buf, nsACString &hash) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr hasher michael@0: = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = hasher->Init(nsICryptoHash::SHA1); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = hasher->Update(reinterpret_cast(buf), michael@0: strlen(buf)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = hasher->Finish(true, hash); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool IsRedirectStatus(uint32_t status) michael@0: { michael@0: // 305 disabled as a security measure (see bug 187996). michael@0: return status == 300 || status == 301 || status == 302 || status == 303 || michael@0: status == 307 || status == 308; michael@0: } michael@0: michael@0: // We only treat 3xx responses as redirects if they have a Location header and michael@0: // the status code is in a whitelist. michael@0: bool michael@0: WillRedirect(const nsHttpResponseHead * response) michael@0: { michael@0: return IsRedirectStatus(response->Status()) && michael@0: response->PeekHeader(nsHttp::Location); michael@0: } michael@0: michael@0: } // unnamed namespace michael@0: michael@0: class AutoRedirectVetoNotifier michael@0: { michael@0: public: michael@0: AutoRedirectVetoNotifier(nsHttpChannel* channel) : mChannel(channel) michael@0: { michael@0: if (mChannel->mHasAutoRedirectVetoNotifier) { michael@0: MOZ_CRASH("Nested AutoRedirectVetoNotifier on the stack"); michael@0: mChannel = nullptr; michael@0: return; michael@0: } michael@0: michael@0: mChannel->mHasAutoRedirectVetoNotifier = true; michael@0: } michael@0: ~AutoRedirectVetoNotifier() {ReportRedirectResult(false);} michael@0: void RedirectSucceeded() {ReportRedirectResult(true);} michael@0: michael@0: private: michael@0: nsHttpChannel* mChannel; michael@0: void ReportRedirectResult(bool succeeded); michael@0: }; michael@0: michael@0: void michael@0: AutoRedirectVetoNotifier::ReportRedirectResult(bool succeeded) michael@0: { michael@0: if (!mChannel) michael@0: return; michael@0: michael@0: mChannel->mRedirectChannel = nullptr; michael@0: michael@0: nsCOMPtr vetoHook; michael@0: NS_QueryNotificationCallbacks(mChannel, michael@0: NS_GET_IID(nsIRedirectResultListener), michael@0: getter_AddRefs(vetoHook)); michael@0: michael@0: nsHttpChannel* channel = mChannel; michael@0: mChannel = nullptr; michael@0: michael@0: if (vetoHook) michael@0: vetoHook->OnRedirectResult(succeeded); michael@0: michael@0: // Drop after the notification michael@0: channel->mHasAutoRedirectVetoNotifier = false; michael@0: michael@0: MOZ_EVENT_TRACER_DONE(channel, "net::http::redirect-callbacks"); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsHttpChannel::nsHttpChannel() michael@0: : HttpAsyncAborter(MOZ_THIS_IN_INITIALIZER_LIST()) michael@0: , mLogicalOffset(0) michael@0: , mPostID(0) michael@0: , mRequestTime(0) michael@0: , mOfflineCacheLastModifiedTime(0) michael@0: , mCachedContentIsValid(false) michael@0: , mCachedContentIsPartial(false) michael@0: , mTransactionReplaced(false) michael@0: , mAuthRetryPending(false) michael@0: , mProxyAuthPending(false) michael@0: , mResuming(false) michael@0: , mInitedCacheEntry(false) michael@0: , mFallbackChannel(false) michael@0: , mCustomConditionalRequest(false) michael@0: , mFallingBack(false) michael@0: , mWaitingForRedirectCallback(false) michael@0: , mRequestTimeInitialized(false) michael@0: , mCacheEntryIsReadOnly(false) michael@0: , mCacheEntryIsWriteOnly(false) michael@0: , mCacheEntriesToWaitFor(0) michael@0: , mHasQueryString(0) michael@0: , mConcurentCacheAccess(0) michael@0: , mIsPartialRequest(0) michael@0: , mHasAutoRedirectVetoNotifier(0) michael@0: , mDidReval(false) michael@0: , mForcePending(false) michael@0: { michael@0: LOG(("Creating nsHttpChannel [this=%p]\n", this)); michael@0: mChannelCreationTime = PR_Now(); michael@0: mChannelCreationTimestamp = TimeStamp::Now(); michael@0: } michael@0: michael@0: nsHttpChannel::~nsHttpChannel() michael@0: { michael@0: LOG(("Destroying nsHttpChannel [this=%p]\n", this)); michael@0: michael@0: if (mAuthProvider) michael@0: mAuthProvider->Disconnect(NS_ERROR_ABORT); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::Init(nsIURI *uri, michael@0: uint32_t caps, michael@0: nsProxyInfo *proxyInfo, michael@0: uint32_t proxyResolveFlags, michael@0: nsIURI *proxyURI) michael@0: { michael@0: #ifdef MOZ_VISUAL_EVENT_TRACER michael@0: nsAutoCString url; michael@0: uri->GetAsciiSpec(url); michael@0: MOZ_EVENT_TRACER_NAME_OBJECT(this, url.get()); michael@0: #endif michael@0: michael@0: nsresult rv = HttpBaseChannel::Init(uri, caps, proxyInfo, michael@0: proxyResolveFlags, proxyURI); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: LOG(("nsHttpChannel::Init [this=%p]\n", this)); michael@0: michael@0: return rv; michael@0: } michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: nsHttpChannel::Connect() michael@0: { michael@0: nsresult rv; michael@0: michael@0: LOG(("nsHttpChannel::Connect [this=%p]\n", this)); michael@0: michael@0: // Even if we're in private browsing mode, we still enforce existing STS michael@0: // data (it is read-only). michael@0: // if the connection is not using SSL and either the exact host matches or michael@0: // a superdomain wants to force HTTPS, do it. michael@0: bool usingSSL = false; michael@0: rv = mURI->SchemeIs("https", &usingSSL); michael@0: NS_ENSURE_SUCCESS(rv,rv); michael@0: michael@0: if (!usingSSL) { michael@0: // enforce Strict-Transport-Security michael@0: nsISiteSecurityService* sss = gHttpHandler->GetSSService(); michael@0: NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: bool isStsHost = false; michael@0: uint32_t flags = mPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0; michael@0: rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, mURI, flags, michael@0: &isStsHost); michael@0: michael@0: // if the SSS check fails, it's likely because this load is on a michael@0: // malformed URI or something else in the setup is wrong, so any error michael@0: // should be reported. michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (isStsHost) { michael@0: LOG(("nsHttpChannel::Connect() STS permissions found\n")); michael@0: return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps); michael@0: } michael@0: } michael@0: michael@0: // ensure that we are using a valid hostname michael@0: if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Host()))) michael@0: return NS_ERROR_UNKNOWN_HOST; michael@0: michael@0: // Finalize ConnectionInfo flags before SpeculativeConnect michael@0: mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0); michael@0: mConnectionInfo->SetPrivate(mPrivateBrowsing); michael@0: michael@0: // Consider opening a TCP connection right away michael@0: RetrieveSSLOptions(); michael@0: SpeculativeConnect(); michael@0: michael@0: // Don't allow resuming when cache must be used michael@0: if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) { michael@0: LOG(("Resuming from cache is not supported yet")); michael@0: return NS_ERROR_DOCUMENT_NOT_CACHED; michael@0: } michael@0: michael@0: if (!gHttpHandler->UseCache()) { michael@0: return ContinueConnect(); michael@0: } michael@0: michael@0: // open a cache entry for this channel... michael@0: rv = OpenCacheEntry(usingSSL); michael@0: michael@0: // do not continue if asyncOpenCacheEntry is in progress michael@0: if (mCacheEntriesToWaitFor) { michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv), "Unexpected state"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("OpenCacheEntry failed [rv=%x]\n", rv)); michael@0: // if this channel is only allowed to pull from the cache, then michael@0: // we must fail if we were unable to open a cache entry. michael@0: if (mLoadFlags & LOAD_ONLY_FROM_CACHE) { michael@0: // If we have a fallback URI (and we're not already michael@0: // falling back), process the fallback asynchronously. michael@0: if (!mFallbackChannel && !mFallbackKey.IsEmpty()) { michael@0: return AsyncCall(&nsHttpChannel::HandleAsyncFallback); michael@0: } michael@0: return NS_ERROR_DOCUMENT_NOT_CACHED; michael@0: } michael@0: // otherwise, let's just proceed without using the cache. michael@0: } michael@0: michael@0: return ContinueConnect(); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ContinueConnect() michael@0: { michael@0: // we may or may not have a cache entry at this point michael@0: if (mCacheEntry) { michael@0: // read straight from the cache if possible... michael@0: if (mCachedContentIsValid) { michael@0: nsRunnableMethod *event = nullptr; michael@0: if (!mCachedContentIsPartial) { michael@0: AsyncCall(&nsHttpChannel::AsyncOnExamineCachedResponse, &event); michael@0: } michael@0: nsresult rv = ReadFromCache(true); michael@0: if (NS_FAILED(rv) && event) { michael@0: event->Revoke(); michael@0: } michael@0: michael@0: AccumulateCacheHitTelemetry(kCacheHit); michael@0: michael@0: return rv; michael@0: } michael@0: else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) { michael@0: // the cache contains the requested resource, but it must be michael@0: // validated before we can reuse it. since we are not allowed michael@0: // to hit the net, there's nothing more to do. the document michael@0: // is effectively not in the cache. michael@0: LOG((" !mCachedContentIsValid && mLoadFlags & LOAD_ONLY_FROM_CACHE")); michael@0: return NS_ERROR_DOCUMENT_NOT_CACHED; michael@0: } michael@0: } michael@0: else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) { michael@0: // If we have a fallback URI (and we're not already michael@0: // falling back), process the fallback asynchronously. michael@0: if (!mFallbackChannel && !mFallbackKey.IsEmpty()) { michael@0: return AsyncCall(&nsHttpChannel::HandleAsyncFallback); michael@0: } michael@0: LOG((" !mCachedEntry && mLoadFlags & LOAD_ONLY_FROM_CACHE")); michael@0: return NS_ERROR_DOCUMENT_NOT_CACHED; michael@0: } michael@0: michael@0: if (mLoadFlags & LOAD_NO_NETWORK_IO) { michael@0: LOG((" mLoadFlags & LOAD_NO_NETWORK_IO")); michael@0: return NS_ERROR_DOCUMENT_NOT_CACHED; michael@0: } michael@0: michael@0: // hit the net... michael@0: nsresult rv = SetupTransaction(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = mTransactionPump->AsyncRead(this, nullptr); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: uint32_t suspendCount = mSuspendCount; michael@0: while (suspendCount--) michael@0: mTransactionPump->Suspend(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::SpeculativeConnect() michael@0: { michael@0: // Before we take the latency hit of dealing with the cache, try and michael@0: // get the TCP (and SSL) handshakes going so they can overlap. michael@0: michael@0: // don't speculate on uses of the offline application cache, michael@0: // if we are offline, when doing http upgrade (i.e. websockets bootstrap), michael@0: // or if we can't do keep-alive (because then we couldn't reuse michael@0: // the speculative connection anyhow). michael@0: if (mApplicationCache || gIOService->IsOffline() || michael@0: mUpgradeProtocolCallback || !(mCaps & NS_HTTP_ALLOW_KEEPALIVE)) michael@0: return; michael@0: michael@0: // LOAD_ONLY_FROM_CACHE and LOAD_NO_NETWORK_IO must not hit network. michael@0: // LOAD_FROM_CACHE and LOAD_CHECK_OFFLINE_CACHE are unlikely to hit network, michael@0: // so skip preconnects for them. michael@0: if (mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_FROM_CACHE | michael@0: LOAD_NO_NETWORK_IO | LOAD_CHECK_OFFLINE_CACHE)) michael@0: return; michael@0: michael@0: nsCOMPtr callbacks; michael@0: NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup, michael@0: getter_AddRefs(callbacks)); michael@0: if (!callbacks) michael@0: return; michael@0: michael@0: gHttpHandler->SpeculativeConnect( michael@0: mConnectionInfo, callbacks, michael@0: mCaps & (NS_HTTP_ALLOW_RSA_FALSESTART | NS_HTTP_DISALLOW_SPDY)); michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::DoNotifyListenerCleanup() michael@0: { michael@0: // We don't need this info anymore michael@0: CleanRedirectCacheChainIfNecessary(); michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::HandleAsyncRedirect() michael@0: { michael@0: NS_PRECONDITION(!mCallOnResume, "How did that happen?"); michael@0: michael@0: if (mSuspendCount) { michael@0: LOG(("Waiting until resume to do async redirect [this=%p]\n", this)); michael@0: mCallOnResume = &nsHttpChannel::HandleAsyncRedirect; michael@0: return; michael@0: } michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: LOG(("nsHttpChannel::HandleAsyncRedirect [this=%p]\n", this)); michael@0: michael@0: // since this event is handled asynchronously, it is possible that this michael@0: // channel could have been canceled, in which case there would be no point michael@0: // in processing the redirect. michael@0: if (NS_SUCCEEDED(mStatus)) { michael@0: PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect); michael@0: rv = AsyncProcessRedirection(mResponseHead->Status()); michael@0: if (NS_FAILED(rv)) { michael@0: PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect); michael@0: // TODO: if !DoNotRender3xxBody(), render redirect body instead. michael@0: // But first we need to cache 3xx bodies (bug 748510) michael@0: ContinueHandleAsyncRedirect(rv); michael@0: } michael@0: } michael@0: else { michael@0: ContinueHandleAsyncRedirect(NS_OK); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ContinueHandleAsyncRedirect(nsresult rv) michael@0: { michael@0: if (NS_FAILED(rv)) { michael@0: // If AsyncProcessRedirection fails, then we have to send out the michael@0: // OnStart/OnStop notifications. michael@0: LOG(("ContinueHandleAsyncRedirect got failure result [rv=%x]\n", rv)); michael@0: mStatus = rv; michael@0: DoNotifyListener(); michael@0: } michael@0: michael@0: // close the cache entry. Blow it away if we couldn't process the redirect michael@0: // for some reason (the cache entry might be corrupt). michael@0: if (mCacheEntry) { michael@0: if (NS_FAILED(rv)) michael@0: mCacheEntry->AsyncDoom(nullptr); michael@0: } michael@0: CloseCacheEntry(false); michael@0: michael@0: mIsPending = false; michael@0: michael@0: if (mLoadGroup) michael@0: mLoadGroup->RemoveRequest(this, nullptr, mStatus); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::HandleAsyncNotModified() michael@0: { michael@0: NS_PRECONDITION(!mCallOnResume, "How did that happen?"); michael@0: michael@0: if (mSuspendCount) { michael@0: LOG(("Waiting until resume to do async not-modified [this=%p]\n", michael@0: this)); michael@0: mCallOnResume = &nsHttpChannel::HandleAsyncNotModified; michael@0: return; michael@0: } michael@0: michael@0: LOG(("nsHttpChannel::HandleAsyncNotModified [this=%p]\n", this)); michael@0: michael@0: DoNotifyListener(); michael@0: michael@0: CloseCacheEntry(true); michael@0: michael@0: mIsPending = false; michael@0: michael@0: if (mLoadGroup) michael@0: mLoadGroup->RemoveRequest(this, nullptr, mStatus); michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::HandleAsyncFallback() michael@0: { michael@0: NS_PRECONDITION(!mCallOnResume, "How did that happen?"); michael@0: michael@0: if (mSuspendCount) { michael@0: LOG(("Waiting until resume to do async fallback [this=%p]\n", this)); michael@0: mCallOnResume = &nsHttpChannel::HandleAsyncFallback; michael@0: return; michael@0: } michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: LOG(("nsHttpChannel::HandleAsyncFallback [this=%p]\n", this)); michael@0: michael@0: // since this event is handled asynchronously, it is possible that this michael@0: // channel could have been canceled, in which case there would be no point michael@0: // in processing the fallback. michael@0: if (!mCanceled) { michael@0: PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback); michael@0: bool waitingForRedirectCallback; michael@0: rv = ProcessFallback(&waitingForRedirectCallback); michael@0: if (waitingForRedirectCallback) michael@0: return; michael@0: PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback); michael@0: } michael@0: michael@0: ContinueHandleAsyncFallback(rv); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ContinueHandleAsyncFallback(nsresult rv) michael@0: { michael@0: if (!mCanceled && (NS_FAILED(rv) || !mFallingBack)) { michael@0: // If ProcessFallback fails, then we have to send out the michael@0: // OnStart/OnStop notifications. michael@0: LOG(("ProcessFallback failed [rv=%x, %d]\n", rv, mFallingBack)); michael@0: mStatus = NS_FAILED(rv) ? rv : NS_ERROR_DOCUMENT_NOT_CACHED; michael@0: DoNotifyListener(); michael@0: } michael@0: michael@0: mIsPending = false; michael@0: michael@0: if (mLoadGroup) michael@0: mLoadGroup->RemoveRequest(this, nullptr, mStatus); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::SetupTransactionLoadGroupInfo() michael@0: { michael@0: // Find the loadgroup at the end of the chain in order michael@0: // to make sure all channels derived from the load group michael@0: // use the same connection scope. michael@0: nsCOMPtr childLoadGroup = do_QueryInterface(mLoadGroup); michael@0: if (!childLoadGroup) michael@0: return; michael@0: michael@0: nsCOMPtr rootLoadGroup; michael@0: childLoadGroup->GetRootLoadGroup(getter_AddRefs(rootLoadGroup)); michael@0: if (!rootLoadGroup) michael@0: return; michael@0: michael@0: // Set the load group connection scope on the transaction michael@0: nsCOMPtr ci; michael@0: rootLoadGroup->GetConnectionInfo(getter_AddRefs(ci)); michael@0: if (ci) michael@0: mTransaction->SetLoadGroupConnectionInfo(ci); michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::RetrieveSSLOptions() michael@0: { michael@0: if (!IsHTTPS() || mPrivateBrowsing) michael@0: return; michael@0: michael@0: nsIPrincipal *principal = GetPrincipal(); michael@0: if (!principal) michael@0: return; michael@0: michael@0: nsCOMPtr permMgr = michael@0: do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); michael@0: if (!permMgr) michael@0: return; michael@0: michael@0: uint32_t perm; michael@0: nsresult rv = permMgr->TestPermissionFromPrincipal(principal, michael@0: "falsestart-rsa", &perm); michael@0: if (NS_SUCCEEDED(rv) && perm == nsIPermissionManager::ALLOW_ACTION) { michael@0: LOG(("nsHttpChannel::RetrieveSSLOptions [this=%p] " michael@0: "falsestart-rsa permission found\n", this)); michael@0: mCaps |= NS_HTTP_ALLOW_RSA_FALSESTART; michael@0: } michael@0: } michael@0: michael@0: static bool michael@0: SafeForPipelining(nsHttpRequestHead::ParsedMethodType method, michael@0: const nsCString &methodString) michael@0: { michael@0: if (method == nsHttpRequestHead::kMethod_Get || michael@0: method == nsHttpRequestHead::kMethod_Head || michael@0: method == nsHttpRequestHead::kMethod_Options) { michael@0: return true; michael@0: } michael@0: michael@0: if (method != nsHttpRequestHead::kMethod_Custom) { michael@0: return false; michael@0: } michael@0: michael@0: return (!strcmp(methodString.get(), "PROPFIND") || michael@0: !strcmp(methodString.get(), "PROPPATCH")); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::SetupTransaction() michael@0: { michael@0: LOG(("nsHttpChannel::SetupTransaction [this=%p]\n", this)); michael@0: michael@0: NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED); michael@0: michael@0: nsresult rv; michael@0: michael@0: if (mCaps & NS_HTTP_ALLOW_PIPELINING) { michael@0: // michael@0: // disable pipelining if: michael@0: // (1) pipelining has been disabled by config michael@0: // (2) pipelining has been disabled by connection mgr info michael@0: // (3) request corresponds to a top-level document load (link click) michael@0: // (4) request method is non-idempotent michael@0: // (5) request is marked slow (e.g XHR) michael@0: // michael@0: if (!mAllowPipelining || michael@0: (mLoadFlags & (LOAD_INITIAL_DOCUMENT_URI | INHIBIT_PIPELINE)) || michael@0: !SafeForPipelining(mRequestHead.ParsedMethod(), mRequestHead.Method())) { michael@0: LOG((" pipelining disallowed\n")); michael@0: mCaps &= ~NS_HTTP_ALLOW_PIPELINING; michael@0: } michael@0: } michael@0: michael@0: if (!mAllowSpdy) michael@0: mCaps |= NS_HTTP_DISALLOW_SPDY; michael@0: michael@0: // Use the URI path if not proxying (transparent proxying such as proxy michael@0: // CONNECT does not count here). Also figure out what HTTP version to use. michael@0: nsAutoCString buf, path; michael@0: nsCString* requestURI; michael@0: if (mConnectionInfo->UsingConnect() || michael@0: !mConnectionInfo->UsingHttpProxy()) { michael@0: rv = mURI->GetPath(path); michael@0: if (NS_FAILED(rv)) return rv; michael@0: // path may contain UTF-8 characters, so ensure that they're escaped. michael@0: if (NS_EscapeURL(path.get(), path.Length(), esc_OnlyNonASCII, buf)) michael@0: requestURI = &buf; michael@0: else michael@0: requestURI = &path; michael@0: mRequestHead.SetVersion(gHttpHandler->HttpVersion()); michael@0: } michael@0: else { michael@0: rv = mURI->GetUserPass(buf); michael@0: if (NS_FAILED(rv)) return rv; michael@0: if (!buf.IsEmpty() && ((strncmp(mSpec.get(), "http:", 5) == 0) || michael@0: strncmp(mSpec.get(), "https:", 6) == 0)) { michael@0: nsCOMPtr tempURI; michael@0: rv = mURI->Clone(getter_AddRefs(tempURI)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = tempURI->SetUserPass(EmptyCString()); michael@0: if (NS_FAILED(rv)) return rv; michael@0: rv = tempURI->GetAsciiSpec(path); michael@0: if (NS_FAILED(rv)) return rv; michael@0: requestURI = &path; michael@0: } michael@0: else michael@0: requestURI = &mSpec; michael@0: mRequestHead.SetVersion(gHttpHandler->ProxyHttpVersion()); michael@0: } michael@0: michael@0: // trim off the #ref portion if any... michael@0: int32_t ref = requestURI->FindChar('#'); michael@0: if (ref != kNotFound) michael@0: requestURI->SetLength(ref); michael@0: michael@0: mRequestHead.SetRequestURI(*requestURI); michael@0: michael@0: // set the request time for cache expiration calculations michael@0: mRequestTime = NowInSeconds(); michael@0: mRequestTimeInitialized = true; michael@0: michael@0: // if doing a reload, force end-to-end michael@0: if (mLoadFlags & LOAD_BYPASS_CACHE) { michael@0: // We need to send 'Pragma:no-cache' to inhibit proxy caching even if michael@0: // no proxy is configured since we might be talking with a transparent michael@0: // proxy, i.e. one that operates at the network level. See bug #14772. michael@0: mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true); michael@0: // If we're configured to speak HTTP/1.1 then also send 'Cache-control: michael@0: // no-cache' michael@0: if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1) michael@0: mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "no-cache", true); michael@0: } michael@0: else if ((mLoadFlags & VALIDATE_ALWAYS) && !mCacheEntryIsWriteOnly) { michael@0: // We need to send 'Cache-Control: max-age=0' to force each cache along michael@0: // the path to the origin server to revalidate its own entry, if any, michael@0: // with the next cache or server. See bug #84847. michael@0: // michael@0: // If we're configured to speak HTTP/1.0 then just send 'Pragma: no-cache' michael@0: if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1) michael@0: mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "max-age=0", true); michael@0: else michael@0: mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true); michael@0: } michael@0: michael@0: if (mResuming) { michael@0: char byteRange[32]; michael@0: PR_snprintf(byteRange, sizeof(byteRange), "bytes=%llu-", mStartPos); michael@0: mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(byteRange)); michael@0: michael@0: if (!mEntityID.IsEmpty()) { michael@0: // Also, we want an error if this resource changed in the meantime michael@0: // Format of the entity id is: escaped_etag/size/lastmod michael@0: nsCString::const_iterator start, end, slash; michael@0: mEntityID.BeginReading(start); michael@0: mEntityID.EndReading(end); michael@0: mEntityID.BeginReading(slash); michael@0: michael@0: if (FindCharInReadable('/', slash, end)) { michael@0: nsAutoCString ifMatch; michael@0: mRequestHead.SetHeader(nsHttp::If_Match, michael@0: NS_UnescapeURL(Substring(start, slash), 0, ifMatch)); michael@0: michael@0: ++slash; // Incrementing, so that searching for '/' won't find michael@0: // the same slash again michael@0: } michael@0: michael@0: if (FindCharInReadable('/', slash, end)) { michael@0: mRequestHead.SetHeader(nsHttp::If_Unmodified_Since, michael@0: Substring(++slash, end)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // create wrapper for this channel's notification callbacks michael@0: nsCOMPtr callbacks; michael@0: NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup, michael@0: getter_AddRefs(callbacks)); michael@0: if (!callbacks) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // create the transaction object michael@0: mTransaction = new nsHttpTransaction(); michael@0: if (!mTransaction) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer. michael@0: if (mLoadFlags & LOAD_ANONYMOUS) michael@0: mCaps |= NS_HTTP_LOAD_ANONYMOUS; michael@0: michael@0: if (mTimingEnabled) michael@0: mCaps |= NS_HTTP_TIMING_ENABLED; michael@0: michael@0: if (mUpgradeProtocolCallback) { michael@0: mRequestHead.SetHeader(nsHttp::Upgrade, mUpgradeProtocol, false); michael@0: mRequestHead.SetHeaderOnce(nsHttp::Connection, michael@0: nsHttp::Upgrade.get(), michael@0: true); michael@0: mCaps |= NS_HTTP_STICKY_CONNECTION; michael@0: mCaps &= ~NS_HTTP_ALLOW_PIPELINING; michael@0: mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE; michael@0: mCaps |= NS_HTTP_DISALLOW_SPDY; michael@0: } michael@0: michael@0: nsCOMPtr responseStream; michael@0: rv = mTransaction->Init(mCaps, mConnectionInfo, &mRequestHead, michael@0: mUploadStream, mUploadStreamHasHeaders, michael@0: NS_GetCurrentThread(), callbacks, this, michael@0: getter_AddRefs(responseStream)); michael@0: if (NS_FAILED(rv)) { michael@0: mTransaction = nullptr; michael@0: return rv; michael@0: } michael@0: michael@0: SetupTransactionLoadGroupInfo(); michael@0: michael@0: rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump), michael@0: responseStream); michael@0: return rv; michael@0: } michael@0: michael@0: // NOTE: This function duplicates code from nsBaseChannel. This will go away michael@0: // once HTTP uses nsBaseChannel (part of bug 312760) michael@0: static void michael@0: CallTypeSniffers(void *aClosure, const uint8_t *aData, uint32_t aCount) michael@0: { michael@0: nsIChannel *chan = static_cast(aClosure); michael@0: michael@0: nsAutoCString newType; michael@0: NS_SniffContent(NS_CONTENT_SNIFFER_CATEGORY, chan, aData, aCount, newType); michael@0: if (!newType.IsEmpty()) { michael@0: chan->SetContentType(newType); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::CallOnStartRequest() michael@0: { michael@0: nsresult rv; michael@0: michael@0: mTracingEnabled = false; michael@0: michael@0: // Allow consumers to override our content type michael@0: if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) { michael@0: // NOTE: We can have both a txn pump and a cache pump when the cache michael@0: // content is partial. In that case, we need to read from the cache, michael@0: // because that's the one that has the initial contents. If that fails michael@0: // then give the transaction pump a shot. michael@0: michael@0: nsIChannel* thisChannel = static_cast(this); michael@0: michael@0: bool typeSniffersCalled = false; michael@0: if (mCachePump) { michael@0: typeSniffersCalled = michael@0: NS_SUCCEEDED(mCachePump->PeekStream(CallTypeSniffers, thisChannel)); michael@0: } michael@0: michael@0: if (!typeSniffersCalled && mTransactionPump) { michael@0: mTransactionPump->PeekStream(CallTypeSniffers, thisChannel); michael@0: } michael@0: } michael@0: michael@0: bool shouldSniff = mResponseHead && (mResponseHead->ContentType().IsEmpty() || michael@0: ((mResponseHead->ContentType().EqualsLiteral(APPLICATION_OCTET_STREAM) && michael@0: (mLoadFlags & LOAD_TREAT_APPLICATION_OCTET_STREAM_AS_UNKNOWN)))); michael@0: michael@0: if (shouldSniff) { michael@0: MOZ_ASSERT(mConnectionInfo, "Should have connection info here"); michael@0: if (!mContentTypeHint.IsEmpty()) michael@0: mResponseHead->SetContentType(mContentTypeHint); michael@0: else if (mResponseHead->Version() == NS_HTTP_VERSION_0_9 && michael@0: mConnectionInfo->Port() != mConnectionInfo->DefaultPort()) michael@0: mResponseHead->SetContentType(NS_LITERAL_CSTRING(TEXT_PLAIN)); michael@0: else { michael@0: // Uh-oh. We had better find out what type we are! michael@0: michael@0: // XXX This does not work with content-encodings... but michael@0: // neither does applying the conversion from the URILoader michael@0: michael@0: nsCOMPtr serv; michael@0: rv = gHttpHandler-> michael@0: GetStreamConverterService(getter_AddRefs(serv)); michael@0: // If we failed, we just fall through to the "normal" case michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsCOMPtr converter; michael@0: rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE, michael@0: "*/*", michael@0: mListener, michael@0: mListenerContext, michael@0: getter_AddRefs(converter)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mListener = converter; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (mResponseHead && mResponseHead->ContentCharset().IsEmpty()) michael@0: mResponseHead->SetContentCharset(mContentCharsetHint); michael@0: michael@0: if (mResponseHead && mCacheEntry) { michael@0: // If we have a cache entry, set its predicted size to ContentLength to michael@0: // avoid caching an entry that will exceed the max size limit. michael@0: rv = mCacheEntry->SetPredictedDataSize( michael@0: mResponseHead->ContentLength()); michael@0: if (NS_ERROR_FILE_TOO_BIG == rv) { michael@0: mCacheEntry = nullptr; michael@0: LOG((" entry too big, throwing away")); michael@0: } else { michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: LOG((" calling mListener->OnStartRequest\n")); michael@0: if (mListener) { michael@0: rv = mListener->OnStartRequest(this, mListenerContext); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } else { michael@0: NS_WARNING("OnStartRequest skipped because of null listener"); michael@0: } michael@0: michael@0: // install stream converter if required michael@0: rv = ApplyContentConversions(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = EnsureAssocReq(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // if this channel is for a download, close off access to the cache. michael@0: if (mCacheEntry && mChannelIsForDownload) { michael@0: mCacheEntry->AsyncDoom(nullptr); michael@0: michael@0: // We must keep the cache entry in case of partial request. michael@0: // Concurrent access is the same, we need the entry in michael@0: // OnStopRequest. michael@0: if (!mCachedContentIsPartial && !mConcurentCacheAccess) michael@0: CloseCacheEntry(false); michael@0: } michael@0: michael@0: if (!mCanceled) { michael@0: // create offline cache entry if offline caching was requested michael@0: if (ShouldUpdateOfflineCacheEntry()) { michael@0: LOG(("writing to the offline cache")); michael@0: rv = InitOfflineCacheEntry(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // InitOfflineCacheEntry may have closed mOfflineCacheEntry michael@0: if (mOfflineCacheEntry) { michael@0: rv = InstallOfflineCacheListener(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: } else if (mApplicationCacheForWrite) { michael@0: LOG(("offline cache is up to date, not updating")); michael@0: CloseOfflineCacheEntry(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ProcessFailedProxyConnect(uint32_t httpStatus) michael@0: { michael@0: // Failure to set up a proxy tunnel via CONNECT means one of the following: michael@0: // 1) Proxy wants authorization, or forbids. michael@0: // 2) DNS at proxy couldn't resolve target URL. michael@0: // 3) Proxy connection to target failed or timed out. michael@0: // 4) Eve intercepted our CONNECT, and is replying with malicious HTML. michael@0: // michael@0: // Our current architecture would parse the proxy's response content with michael@0: // the permission of the target URL. Given #4, we must avoid rendering the michael@0: // body of the reply, and instead give the user a (hopefully helpful) michael@0: // boilerplate error page, based on just the HTTP status of the reply. michael@0: michael@0: MOZ_ASSERT(mConnectionInfo->UsingConnect(), michael@0: "proxy connect failed but not using CONNECT?"); michael@0: nsresult rv; michael@0: switch (httpStatus) michael@0: { michael@0: case 300: case 301: case 302: case 303: case 307: case 308: michael@0: // Bad redirect: not top-level, or it's a POST, bad/missing Location, michael@0: // or ProcessRedirect() failed for some other reason. Legal michael@0: // redirects that fail because site not available, etc., are handled michael@0: // elsewhere, in the regular codepath. michael@0: rv = NS_ERROR_CONNECTION_REFUSED; michael@0: break; michael@0: case 403: // HTTP/1.1: "Forbidden" michael@0: case 407: // ProcessAuthentication() failed michael@0: case 501: // HTTP/1.1: "Not Implemented" michael@0: // user sees boilerplate Mozilla "Proxy Refused Connection" page. michael@0: rv = NS_ERROR_PROXY_CONNECTION_REFUSED; michael@0: break; michael@0: // Squid sends 404 if DNS fails (regular 404 from target is tunneled) michael@0: case 404: // HTTP/1.1: "Not Found" michael@0: // RFC 2616: "some deployed proxies are known to return 400 or 500 when michael@0: // DNS lookups time out." (Squid uses 500 if it runs out of sockets: so michael@0: // we have a conflict here). michael@0: case 400: // HTTP/1.1 "Bad Request" michael@0: case 500: // HTTP/1.1: "Internal Server Error" michael@0: /* User sees: "Address Not Found: Firefox can't find the server at michael@0: * www.foo.com." michael@0: */ michael@0: rv = NS_ERROR_UNKNOWN_HOST; michael@0: break; michael@0: case 502: // HTTP/1.1: "Bad Gateway" (invalid resp from target server) michael@0: // Squid returns 503 if target request fails for anything but DNS. michael@0: case 503: // HTTP/1.1: "Service Unavailable" michael@0: /* User sees: "Failed to Connect: michael@0: * Firefox can't establish a connection to the server at michael@0: * www.foo.com. Though the site seems valid, the browser michael@0: * was unable to establish a connection." michael@0: */ michael@0: rv = NS_ERROR_CONNECTION_REFUSED; michael@0: break; michael@0: // RFC 2616 uses 504 for both DNS and target timeout, so not clear what to michael@0: // do here: picking target timeout, as DNS covered by 400/404/500 michael@0: case 504: // HTTP/1.1: "Gateway Timeout" michael@0: // user sees: "Network Timeout: The server at www.foo.com michael@0: // is taking too long to respond." michael@0: rv = NS_ERROR_NET_TIMEOUT; michael@0: break; michael@0: // Confused proxy server or malicious response michael@0: default: michael@0: rv = NS_ERROR_PROXY_CONNECTION_REFUSED; michael@0: break; michael@0: } michael@0: LOG(("Cancelling failed proxy CONNECT [this=%p httpStatus=%u]\n", michael@0: this, httpStatus)); michael@0: Cancel(rv); michael@0: CallOnStartRequest(); michael@0: return rv; michael@0: } michael@0: michael@0: /** michael@0: * Decide whether or not to remember Strict-Transport-Security, and whether michael@0: * or not to enforce channel integrity. michael@0: * michael@0: * @return NS_ERROR_FAILURE if there's security information missing even though michael@0: * it's an HTTPS connection. michael@0: */ michael@0: nsresult michael@0: nsHttpChannel::ProcessSTSHeader() michael@0: { michael@0: nsresult rv; michael@0: bool isHttps = false; michael@0: rv = mURI->SchemeIs("https", &isHttps); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // If this channel is not loading securely, STS doesn't do anything. michael@0: // The upgrade to HTTPS takes place earlier in the channel load process. michael@0: if (!isHttps) michael@0: return NS_OK; michael@0: michael@0: nsAutoCString asciiHost; michael@0: rv = mURI->GetAsciiHost(asciiHost); michael@0: NS_ENSURE_SUCCESS(rv, NS_OK); michael@0: michael@0: // If the channel is not a hostname, but rather an IP, STS doesn't do michael@0: // anything. michael@0: PRNetAddr hostAddr; michael@0: if (PR_SUCCESS == PR_StringToNetAddr(asciiHost.get(), &hostAddr)) michael@0: return NS_OK; michael@0: michael@0: nsISiteSecurityService* sss = gHttpHandler->GetSSService(); michael@0: NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: // mSecurityInfo may not always be present, and if it's not then it is okay michael@0: // to just disregard any STS headers since we know nothing about the michael@0: // security of the connection. michael@0: NS_ENSURE_TRUE(mSecurityInfo, NS_OK); michael@0: michael@0: // Check the trustworthiness of the channel (are there any cert errors?) michael@0: // If there are certificate errors, we still load the data, we just ignore michael@0: // any STS headers that are present. michael@0: bool tlsIsBroken = false; michael@0: rv = sss->ShouldIgnoreHeaders(mSecurityInfo, &tlsIsBroken); michael@0: NS_ENSURE_SUCCESS(rv, NS_OK); michael@0: michael@0: // If this was already an STS host, the connection should have been aborted michael@0: // by the bad cert handler in the case of cert errors. If it didn't abort the connection, michael@0: // there's probably something funny going on. michael@0: // If this wasn't an STS host, errors are allowed, but no more STS processing michael@0: // will happen during the session. michael@0: bool wasAlreadySTSHost; michael@0: uint32_t flags = michael@0: NS_UsePrivateBrowsing(this) ? nsISocketProvider::NO_PERMANENT_STORAGE : 0; michael@0: rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, mURI, flags, michael@0: &wasAlreadySTSHost); michael@0: // Failure here means STS is broken. Don't prevent the load, but this michael@0: // shouldn't fail. michael@0: NS_ENSURE_SUCCESS(rv, NS_OK); michael@0: MOZ_ASSERT(!(wasAlreadySTSHost && tlsIsBroken), michael@0: "connection should have been aborted by nss-bad-cert-handler"); michael@0: michael@0: // Any STS header is ignored if the channel is not trusted due to michael@0: // certificate errors (STS Spec 7.1) -- there is nothing else to do, and michael@0: // the load may progress. michael@0: if (tlsIsBroken) { michael@0: LOG(("STS: Transport layer is not trustworthy, ignoring " michael@0: "STS headers and continuing load\n")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If there's a STS header, process it (STS Spec 7.1). At this point in michael@0: // processing, the channel is trusted, so the header should not be ignored. michael@0: const nsHttpAtom atom = nsHttp::ResolveAtom("Strict-Transport-Security"); michael@0: nsAutoCString stsHeader; michael@0: rv = mResponseHead->GetHeader(atom, stsHeader); michael@0: if (rv == NS_ERROR_NOT_AVAILABLE) { michael@0: LOG(("STS: No STS header, continuing load.\n")); michael@0: return NS_OK; michael@0: } michael@0: // All other failures are fatal. michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = sss->ProcessHeader(nsISiteSecurityService::HEADER_HSTS, mURI, michael@0: stsHeader.get(), flags, nullptr, nullptr); michael@0: if (NS_FAILED(rv)) { michael@0: AddSecurityMessage(NS_LITERAL_STRING("InvalidSTSHeaders"), michael@0: NS_LITERAL_STRING("Invalid HSTS Headers")); michael@0: LOG(("STS: Failed to parse STS header, continuing load.\n")); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsHttpChannel::IsHTTPS() michael@0: { michael@0: bool isHttps; michael@0: if (NS_FAILED(mURI->SchemeIs("https", &isHttps)) || !isHttps) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::ProcessSSLInformation() michael@0: { michael@0: // If this is HTTPS, record any use of RSA so that Key Exchange Algorithm michael@0: // can be whitelisted for TLS False Start in future sessions. We could michael@0: // do the same for DH but its rarity doesn't justify the lookup. michael@0: michael@0: if (mCanceled || NS_FAILED(mStatus) || !mSecurityInfo || michael@0: !IsHTTPS() || mPrivateBrowsing) michael@0: return; michael@0: michael@0: nsCOMPtr ssl = do_QueryInterface(mSecurityInfo); michael@0: nsCOMPtr statusProvider = michael@0: do_QueryInterface(mSecurityInfo); michael@0: if (!ssl || !statusProvider) michael@0: return; michael@0: nsCOMPtr sslstat; michael@0: statusProvider->GetSSLStatus(getter_AddRefs(sslstat)); michael@0: if (!sslstat) michael@0: return; michael@0: michael@0: // If certificate exceptions are being used don't record this information michael@0: // in the permission manager. michael@0: bool trustCheck; michael@0: if (NS_FAILED(sslstat->GetIsDomainMismatch(&trustCheck)) || trustCheck) michael@0: return; michael@0: if (NS_FAILED(sslstat->GetIsNotValidAtThisTime(&trustCheck)) || trustCheck) michael@0: return; michael@0: if (NS_FAILED(sslstat->GetIsUntrusted(&trustCheck)) || trustCheck) michael@0: return; michael@0: michael@0: int16_t kea = ssl->GetKEAUsed(); michael@0: michael@0: nsIPrincipal *principal = GetPrincipal(); michael@0: if (!principal) michael@0: return; michael@0: michael@0: // set a permission manager flag that future transactions can michael@0: // use via RetrieveSSLOptions(() michael@0: michael@0: nsCOMPtr permMgr = michael@0: do_GetService(NS_PERMISSIONMANAGER_CONTRACTID); michael@0: if (!permMgr) michael@0: return; michael@0: michael@0: // Allow this to stand for a week michael@0: int64_t expireTime = (PR_Now() / PR_USEC_PER_MSEC) + michael@0: (86400 * 7 * PR_MSEC_PER_SEC); michael@0: michael@0: if (kea == ssl_kea_rsa) { michael@0: permMgr->AddFromPrincipal(principal, "falsestart-rsa", michael@0: nsIPermissionManager::ALLOW_ACTION, michael@0: nsIPermissionManager::EXPIRE_TIME, michael@0: expireTime); michael@0: LOG(("nsHttpChannel::ProcessSSLInformation [this=%p] " michael@0: "falsestart-rsa permission granted for this host\n", this)); michael@0: } else { michael@0: permMgr->RemoveFromPrincipal(principal, "falsestart-rsa"); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ProcessResponse() michael@0: { michael@0: nsresult rv; michael@0: uint32_t httpStatus = mResponseHead->Status(); michael@0: michael@0: // Gather data on whether the transaction and page (if this is michael@0: // the initial page load) is being loaded with SSL. michael@0: Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_IS_SSL, michael@0: mConnectionInfo->UsingSSL()); michael@0: if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) { michael@0: Telemetry::Accumulate(Telemetry::HTTP_PAGELOAD_IS_SSL, michael@0: mConnectionInfo->UsingSSL()); michael@0: } michael@0: michael@0: LOG(("nsHttpChannel::ProcessResponse [this=%p httpStatus=%u]\n", michael@0: this, httpStatus)); michael@0: michael@0: if (mTransaction->ProxyConnectFailed()) { michael@0: // Only allow 407 (authentication required) to continue michael@0: if (httpStatus != 407) michael@0: return ProcessFailedProxyConnect(httpStatus); michael@0: // If proxy CONNECT response needs to complete, wait to process connection michael@0: // for Strict-Transport-Security. michael@0: } else { michael@0: // Given a successful connection, process any STS data that's relevant. michael@0: rv = ProcessSTSHeader(); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv), "ProcessSTSHeader failed, continuing load."); michael@0: } michael@0: michael@0: MOZ_ASSERT(!mCachedContentIsValid); michael@0: michael@0: ProcessSSLInformation(); michael@0: michael@0: // notify "http-on-examine-response" observers michael@0: gHttpHandler->OnExamineResponse(this); michael@0: michael@0: SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie)); michael@0: michael@0: // handle unused username and password in url (see bug 232567) michael@0: if (httpStatus != 401 && httpStatus != 407) { michael@0: if (!mAuthRetryPending) michael@0: mAuthProvider->CheckForSuperfluousAuth(); michael@0: if (mCanceled) michael@0: return CallOnStartRequest(); michael@0: michael@0: // reset the authentication's current continuation state because our michael@0: // last authentication attempt has been completed successfully michael@0: mAuthProvider->Disconnect(NS_ERROR_ABORT); michael@0: mAuthProvider = nullptr; michael@0: LOG((" continuation state has been reset")); michael@0: } michael@0: michael@0: bool successfulReval = false; michael@0: michael@0: // handle different server response categories. Note that we handle michael@0: // caching or not caching of error pages in michael@0: // nsHttpResponseHead::MustValidate; if you change this switch, update that michael@0: // one michael@0: switch (httpStatus) { michael@0: case 200: michael@0: case 203: michael@0: // Per RFC 2616, 14.35.2, "A server MAY ignore the Range header". michael@0: // So if a server does that and sends 200 instead of 206 that we michael@0: // expect, notify our caller. michael@0: // However, if we wanted to start from the beginning, let it go through michael@0: if (mResuming && mStartPos != 0) { michael@0: LOG(("Server ignored our Range header, cancelling [this=%p]\n", this)); michael@0: Cancel(NS_ERROR_NOT_RESUMABLE); michael@0: rv = CallOnStartRequest(); michael@0: break; michael@0: } michael@0: // these can normally be cached michael@0: rv = ProcessNormal(); michael@0: MaybeInvalidateCacheEntryForSubsequentGet(); michael@0: break; michael@0: case 206: michael@0: if (mCachedContentIsPartial) // an internal byte range request... michael@0: rv = ProcessPartialContent(); michael@0: else { michael@0: mCacheInputStream.CloseAndRelease(); michael@0: rv = ProcessNormal(); michael@0: } michael@0: break; michael@0: case 300: michael@0: case 301: michael@0: case 302: michael@0: case 307: michael@0: case 308: michael@0: case 303: michael@0: #if 0 michael@0: case 305: // disabled as a security measure (see bug 187996). michael@0: #endif michael@0: // don't store the response body for redirects michael@0: MaybeInvalidateCacheEntryForSubsequentGet(); michael@0: PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse); michael@0: rv = AsyncProcessRedirection(httpStatus); michael@0: if (NS_FAILED(rv)) { michael@0: PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse); michael@0: LOG(("AsyncProcessRedirection failed [rv=%x]\n", rv)); michael@0: // don't cache failed redirect responses. michael@0: if (mCacheEntry) michael@0: mCacheEntry->AsyncDoom(nullptr); michael@0: if (DoNotRender3xxBody(rv)) { michael@0: mStatus = rv; michael@0: DoNotifyListener(); michael@0: } else { michael@0: rv = ContinueProcessResponse(rv); michael@0: } michael@0: } michael@0: break; michael@0: case 304: michael@0: rv = ProcessNotModified(); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("ProcessNotModified failed [rv=%x]\n", rv)); michael@0: mCacheInputStream.CloseAndRelease(); michael@0: rv = ProcessNormal(); michael@0: } michael@0: else { michael@0: successfulReval = true; michael@0: } michael@0: break; michael@0: case 401: michael@0: case 407: michael@0: rv = mAuthProvider->ProcessAuthentication( michael@0: httpStatus, mConnectionInfo->UsingSSL() && michael@0: mTransaction->ProxyConnectFailed()); michael@0: if (rv == NS_ERROR_IN_PROGRESS) { michael@0: // authentication prompt has been invoked and result michael@0: // is expected asynchronously michael@0: mAuthRetryPending = true; michael@0: if (httpStatus == 407 || mTransaction->ProxyConnectFailed()) michael@0: mProxyAuthPending = true; michael@0: michael@0: // suspend the transaction pump to stop receiving the michael@0: // unauthenticated content data. We will throw that data michael@0: // away when user provides credentials or resume the pump michael@0: // when user refuses to authenticate. michael@0: LOG(("Suspending the transaction, asynchronously prompting for credentials")); michael@0: mTransactionPump->Suspend(); michael@0: rv = NS_OK; michael@0: } michael@0: else if (NS_FAILED(rv)) { michael@0: LOG(("ProcessAuthentication failed [rv=%x]\n", rv)); michael@0: if (mTransaction->ProxyConnectFailed()) michael@0: return ProcessFailedProxyConnect(httpStatus); michael@0: if (!mAuthRetryPending) michael@0: mAuthProvider->CheckForSuperfluousAuth(); michael@0: rv = ProcessNormal(); michael@0: } michael@0: else michael@0: mAuthRetryPending = true; // see DoAuthRetry michael@0: break; michael@0: default: michael@0: rv = ProcessNormal(); michael@0: MaybeInvalidateCacheEntryForSubsequentGet(); michael@0: break; michael@0: } michael@0: michael@0: CacheDisposition cacheDisposition; michael@0: if (!mDidReval) michael@0: cacheDisposition = kCacheMissed; michael@0: else if (successfulReval) michael@0: cacheDisposition = kCacheHitViaReval; michael@0: else michael@0: cacheDisposition = kCacheMissedViaReval; michael@0: michael@0: AccumulateCacheHitTelemetry(cacheDisposition); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ContinueProcessResponse(nsresult rv) michael@0: { michael@0: bool doNotRender = DoNotRender3xxBody(rv); michael@0: michael@0: if (rv == NS_ERROR_DOM_BAD_URI && mRedirectURI) { michael@0: bool isHTTP = false; michael@0: if (NS_FAILED(mRedirectURI->SchemeIs("http", &isHTTP))) michael@0: isHTTP = false; michael@0: if (!isHTTP && NS_FAILED(mRedirectURI->SchemeIs("https", &isHTTP))) michael@0: isHTTP = false; michael@0: michael@0: if (!isHTTP) { michael@0: // This was a blocked attempt to redirect and subvert the system by michael@0: // redirecting to another protocol (perhaps javascript:) michael@0: // In that case we want to throw an error instead of displaying the michael@0: // non-redirected response body. michael@0: LOG(("ContinueProcessResponse detected rejected Non-HTTP Redirection")); michael@0: doNotRender = true; michael@0: rv = NS_ERROR_CORRUPTED_CONTENT; michael@0: } michael@0: } michael@0: michael@0: if (doNotRender) { michael@0: Cancel(rv); michael@0: DoNotifyListener(); michael@0: return rv; michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: UpdateInhibitPersistentCachingFlag(); michael@0: michael@0: InitCacheEntry(); michael@0: CloseCacheEntry(false); michael@0: michael@0: if (mApplicationCacheForWrite) { michael@0: // Store response in the offline cache michael@0: InitOfflineCacheEntry(); michael@0: CloseOfflineCacheEntry(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: LOG(("ContinueProcessResponse got failure result [rv=%x]\n", rv)); michael@0: if (mTransaction->ProxyConnectFailed()) { michael@0: return ProcessFailedProxyConnect(mRedirectType); michael@0: } michael@0: return ProcessNormal(); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ProcessNormal() michael@0: { michael@0: nsresult rv; michael@0: michael@0: LOG(("nsHttpChannel::ProcessNormal [this=%p]\n", this)); michael@0: michael@0: bool succeeded; michael@0: rv = GetRequestSucceeded(&succeeded); michael@0: if (NS_SUCCEEDED(rv) && !succeeded) { michael@0: PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal); michael@0: bool waitingForRedirectCallback; michael@0: (void)ProcessFallback(&waitingForRedirectCallback); michael@0: if (waitingForRedirectCallback) { michael@0: // The transaction has been suspended by ProcessFallback. michael@0: return NS_OK; michael@0: } michael@0: PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal); michael@0: } michael@0: michael@0: return ContinueProcessNormal(NS_OK); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ContinueProcessNormal(nsresult rv) michael@0: { michael@0: if (NS_FAILED(rv)) { michael@0: // Fill the failure status here, we have failed to fall back, thus we michael@0: // have to report our status as failed. michael@0: mStatus = rv; michael@0: DoNotifyListener(); michael@0: return rv; michael@0: } michael@0: michael@0: if (mFallingBack) { michael@0: // Do not continue with normal processing, fallback is in michael@0: // progress now. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // if we're here, then any byte-range requests failed to result in a partial michael@0: // response. we must clear this flag to prevent BufferPartialContent from michael@0: // being called inside our OnDataAvailable (see bug 136678). michael@0: mCachedContentIsPartial = false; michael@0: michael@0: ClearBogusContentEncodingIfNeeded(); michael@0: michael@0: UpdateInhibitPersistentCachingFlag(); michael@0: michael@0: // this must be called before firing OnStartRequest, since http clients, michael@0: // such as imagelib, expect our cache entry to already have the correct michael@0: // expiration time (bug 87710). michael@0: if (mCacheEntry) { michael@0: rv = InitCacheEntry(); michael@0: if (NS_FAILED(rv)) michael@0: CloseCacheEntry(true); michael@0: } michael@0: michael@0: // Check that the server sent us what we were asking for michael@0: if (mResuming) { michael@0: // Create an entity id from the response michael@0: nsAutoCString id; michael@0: rv = GetEntityID(id); michael@0: if (NS_FAILED(rv)) { michael@0: // If creating an entity id is not possible -> error michael@0: Cancel(NS_ERROR_NOT_RESUMABLE); michael@0: } michael@0: else if (mResponseHead->Status() != 206 && michael@0: mResponseHead->Status() != 200) { michael@0: // Probably 404 Not Found, 412 Precondition Failed or michael@0: // 416 Invalid Range -> error michael@0: LOG(("Unexpected response status while resuming, aborting [this=%p]\n", michael@0: this)); michael@0: Cancel(NS_ERROR_ENTITY_CHANGED); michael@0: } michael@0: // If we were passed an entity id, verify it's equal to the server's michael@0: else if (!mEntityID.IsEmpty()) { michael@0: if (!mEntityID.Equals(id)) { michael@0: LOG(("Entity mismatch, expected '%s', got '%s', aborting [this=%p]", michael@0: mEntityID.get(), id.get(), this)); michael@0: Cancel(NS_ERROR_ENTITY_CHANGED); michael@0: } michael@0: } michael@0: } michael@0: michael@0: rv = CallOnStartRequest(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // install cache listener if we still have a cache entry open michael@0: if (mCacheEntry && !mLoadedFromApplicationCache) { michael@0: rv = InstallCacheListener(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::PromptTempRedirect() michael@0: { michael@0: if (!gHttpHandler->PromptTempRedirect()) { michael@0: return NS_OK; michael@0: } michael@0: nsresult rv; michael@0: nsCOMPtr bundleService = michael@0: do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr stringBundle; michael@0: rv = bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(stringBundle)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsXPIDLString messageString; michael@0: rv = stringBundle->GetStringFromName(MOZ_UTF16("RepostFormData"), getter_Copies(messageString)); michael@0: // GetStringFromName can return NS_OK and nullptr messageString. michael@0: if (NS_SUCCEEDED(rv) && messageString) { michael@0: bool repost = false; michael@0: michael@0: nsCOMPtr prompt; michael@0: GetCallback(prompt); michael@0: if (!prompt) michael@0: return NS_ERROR_NO_INTERFACE; michael@0: michael@0: prompt->Confirm(nullptr, messageString, &repost); michael@0: if (!repost) michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ProxyFailover() michael@0: { michael@0: LOG(("nsHttpChannel::ProxyFailover [this=%p]\n", this)); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr pps = michael@0: do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCOMPtr pi; michael@0: rv = pps->GetFailoverForProxy(mConnectionInfo->ProxyInfo(), mURI, mStatus, michael@0: getter_AddRefs(pi)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // XXXbz so where does this codepath remove us from the loadgroup, michael@0: // exactly? michael@0: return AsyncDoReplaceWithProxy(pi); michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::HandleAsyncRedirectChannelToHttps() michael@0: { michael@0: NS_PRECONDITION(!mCallOnResume, "How did that happen?"); michael@0: michael@0: if (mSuspendCount) { michael@0: LOG(("Waiting until resume to do async redirect to https [this=%p]\n", this)); michael@0: mCallOnResume = &nsHttpChannel::HandleAsyncRedirectChannelToHttps; michael@0: return; michael@0: } michael@0: michael@0: nsresult rv = StartRedirectChannelToHttps(); michael@0: if (NS_FAILED(rv)) michael@0: ContinueAsyncRedirectChannelToURI(rv); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::StartRedirectChannelToHttps() michael@0: { michael@0: nsresult rv = NS_OK; michael@0: LOG(("nsHttpChannel::HandleAsyncRedirectChannelToHttps() [STS]\n")); michael@0: michael@0: nsCOMPtr upgradedURI; michael@0: michael@0: rv = mURI->Clone(getter_AddRefs(upgradedURI)); michael@0: NS_ENSURE_SUCCESS(rv,rv); michael@0: michael@0: upgradedURI->SetScheme(NS_LITERAL_CSTRING("https")); michael@0: michael@0: int32_t oldPort = -1; michael@0: rv = mURI->GetPort(&oldPort); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Keep any nonstandard ports so only the scheme is changed. michael@0: // For example: michael@0: // http://foo.com:80 -> https://foo.com:443 michael@0: // http://foo.com:81 -> https://foo.com:81 michael@0: michael@0: if (oldPort == 80 || oldPort == -1) michael@0: upgradedURI->SetPort(-1); michael@0: else michael@0: upgradedURI->SetPort(oldPort); michael@0: michael@0: return StartRedirectChannelToURI(upgradedURI, michael@0: nsIChannelEventSink::REDIRECT_PERMANENT); michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::HandleAsyncAPIRedirect() michael@0: { michael@0: NS_PRECONDITION(!mCallOnResume, "How did that happen?"); michael@0: NS_PRECONDITION(mAPIRedirectToURI, "How did that happen?"); michael@0: michael@0: if (mSuspendCount) { michael@0: LOG(("Waiting until resume to do async API redirect [this=%p]\n", this)); michael@0: mCallOnResume = &nsHttpChannel::HandleAsyncAPIRedirect; michael@0: return; michael@0: } michael@0: michael@0: nsresult rv = StartRedirectChannelToURI(mAPIRedirectToURI, michael@0: nsIChannelEventSink::REDIRECT_PERMANENT); michael@0: if (NS_FAILED(rv)) michael@0: ContinueAsyncRedirectChannelToURI(rv); michael@0: michael@0: return; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::StartRedirectChannelToURI(nsIURI *upgradedURI, uint32_t flags) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: LOG(("nsHttpChannel::StartRedirectChannelToURI()\n")); michael@0: michael@0: nsCOMPtr newChannel; michael@0: michael@0: nsCOMPtr ioService; michael@0: rv = gHttpHandler->GetIOService(getter_AddRefs(ioService)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = ioService->NewChannelFromURI(upgradedURI, getter_AddRefs(newChannel)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = SetupReplacementChannel(upgradedURI, newChannel, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Inform consumers about this fake redirect michael@0: mRedirectChannel = newChannel; michael@0: michael@0: PushRedirectAsyncFunc( michael@0: &nsHttpChannel::ContinueAsyncRedirectChannelToURI); michael@0: rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags); michael@0: michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = WaitForRedirectCallback(); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: AutoRedirectVetoNotifier notifier(this); michael@0: michael@0: /* Remove the async call to ContinueAsyncRedirectChannelToURI(). michael@0: * It is called directly by our callers upon return (to clean up michael@0: * the failed redirect). */ michael@0: PopRedirectAsyncFunc( michael@0: &nsHttpChannel::ContinueAsyncRedirectChannelToURI); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ContinueAsyncRedirectChannelToURI(nsresult rv) michael@0: { michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = OpenRedirectChannel(rv); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: // Fill the failure status here, the update to https had been vetoed michael@0: // but from the security reasons we have to discard the whole channel michael@0: // load. michael@0: mStatus = rv; michael@0: } michael@0: michael@0: if (mLoadGroup) michael@0: mLoadGroup->RemoveRequest(this, nullptr, mStatus); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: // We have to manually notify the listener because there is not any pump michael@0: // that would call our OnStart/StopRequest after resume from waiting for michael@0: // the redirect callback. michael@0: DoNotifyListener(); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::OpenRedirectChannel(nsresult rv) michael@0: { michael@0: AutoRedirectVetoNotifier notifier(this); michael@0: michael@0: // Make sure to do this _after_ calling OnChannelRedirect michael@0: mRedirectChannel->SetOriginalURI(mOriginalURI); michael@0: michael@0: // And now, notify observers the deprecated way michael@0: nsCOMPtr httpEventSink; michael@0: GetCallback(httpEventSink); michael@0: if (httpEventSink) { michael@0: // NOTE: nsIHttpEventSink is only used for compatibility with pre-1.8 michael@0: // versions. michael@0: rv = httpEventSink->OnRedirect(this, mRedirectChannel); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: // open new channel michael@0: rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: mStatus = NS_BINDING_REDIRECTED; michael@0: michael@0: notifier.RedirectSucceeded(); michael@0: michael@0: ReleaseListeners(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::AsyncDoReplaceWithProxy(nsIProxyInfo* pi) michael@0: { michael@0: LOG(("nsHttpChannel::AsyncDoReplaceWithProxy [this=%p pi=%p]", this, pi)); michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr newChannel; michael@0: rv = gHttpHandler->NewProxiedChannel(mURI, pi, mProxyResolveFlags, michael@0: mProxyURI, getter_AddRefs(newChannel)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = SetupReplacementChannel(mURI, newChannel, true); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // Inform consumers about this fake redirect michael@0: mRedirectChannel = newChannel; michael@0: uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL; michael@0: michael@0: PushRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy); michael@0: rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags); michael@0: michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = WaitForRedirectCallback(); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: AutoRedirectVetoNotifier notifier(this); michael@0: PopRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ContinueDoReplaceWithProxy(nsresult rv) michael@0: { michael@0: AutoRedirectVetoNotifier notifier(this); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: NS_PRECONDITION(mRedirectChannel, "No redirect channel?"); michael@0: michael@0: // Make sure to do this _after_ calling OnChannelRedirect michael@0: mRedirectChannel->SetOriginalURI(mOriginalURI); michael@0: michael@0: // open new channel michael@0: rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: mStatus = NS_BINDING_REDIRECTED; michael@0: michael@0: notifier.RedirectSucceeded(); michael@0: michael@0: ReleaseListeners(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ResolveProxy() michael@0: { michael@0: LOG(("nsHttpChannel::ResolveProxy [this=%p]\n", this)); michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr pps = michael@0: do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // using the nsIProtocolProxyService2 allows a minor performance michael@0: // optimization, but if an add-on has only provided the original interface michael@0: // then it is ok to use that version. michael@0: nsCOMPtr pps2 = do_QueryInterface(pps); michael@0: if (pps2) { michael@0: rv = pps2->AsyncResolve2(this, mProxyResolveFlags, michael@0: this, getter_AddRefs(mProxyRequest)); michael@0: } else { michael@0: rv = pps->AsyncResolve(this, mProxyResolveFlags, michael@0: this, getter_AddRefs(mProxyRequest)); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: bool michael@0: nsHttpChannel::ResponseWouldVary(nsICacheEntry* entry) const michael@0: { michael@0: nsresult rv; michael@0: nsAutoCString buf, metaKey; michael@0: mCachedResponseHead->GetHeader(nsHttp::Vary, buf); michael@0: if (!buf.IsEmpty()) { michael@0: NS_NAMED_LITERAL_CSTRING(prefix, "request-"); michael@0: michael@0: // enumerate the elements of the Vary header... michael@0: char *val = buf.BeginWriting(); // going to munge buf michael@0: char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val); michael@0: while (token) { michael@0: LOG(("nsHttpChannel::ResponseWouldVary [channel=%p] " \ michael@0: "processing %s\n", michael@0: this, token)); michael@0: // michael@0: // if "*", then assume response would vary. technically speaking, michael@0: // "Vary: header, *" is not permitted, but we allow it anyways. michael@0: // michael@0: // We hash values of cookie-headers for the following reasons: michael@0: // michael@0: // 1- cookies can be very large in size michael@0: // michael@0: // 2- cookies may contain sensitive information. (for parity with michael@0: // out policy of not storing Set-cookie headers in the cache michael@0: // meta data, we likewise do not want to store cookie headers michael@0: // here.) michael@0: // michael@0: if (*token == '*') michael@0: return true; // if we encounter this, just get out of here michael@0: michael@0: // build cache meta data key... michael@0: metaKey = prefix + nsDependentCString(token); michael@0: michael@0: // check the last value of the given request header to see if it has michael@0: // since changed. if so, then indeed the cached response is invalid. michael@0: nsXPIDLCString lastVal; michael@0: entry->GetMetaDataElement(metaKey.get(), getter_Copies(lastVal)); michael@0: LOG(("nsHttpChannel::ResponseWouldVary [channel=%p] " michael@0: "stored value = \"%s\"\n", michael@0: this, lastVal.get())); michael@0: michael@0: // Look for value of "Cookie" in the request headers michael@0: nsHttpAtom atom = nsHttp::ResolveAtom(token); michael@0: const char *newVal = mRequestHead.PeekHeader(atom); michael@0: if (!lastVal.IsEmpty()) { michael@0: // value for this header in cache, but no value in request michael@0: if (!newVal) michael@0: return true; // yes - response would vary michael@0: michael@0: // If this is a cookie-header, stored metadata is not michael@0: // the value itself but the hash. So we also hash the michael@0: // outgoing value here in order to compare the hashes michael@0: nsAutoCString hash; michael@0: if (atom == nsHttp::Cookie) { michael@0: rv = Hash(newVal, hash); michael@0: // If hash failed, be conservative (the cached hash michael@0: // exists at this point) and claim response would vary michael@0: if (NS_FAILED(rv)) michael@0: return true; michael@0: newVal = hash.get(); michael@0: michael@0: LOG(("nsHttpChannel::ResponseWouldVary [this=%p] " \ michael@0: "set-cookie value hashed to %s\n", michael@0: this, newVal)); michael@0: } michael@0: michael@0: if (strcmp(newVal, lastVal)) michael@0: return true; // yes, response would vary michael@0: michael@0: } else if (newVal) { // old value is empty, but newVal is set michael@0: return true; michael@0: } michael@0: michael@0: // next token... michael@0: token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val); michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: // We need to have an implementation of this function just so that we can keep michael@0: // all references to mCallOnResume of type nsHttpChannel: it's not OK in C++ michael@0: // to set a member function ptr to a base class function. michael@0: void michael@0: nsHttpChannel::HandleAsyncAbort() michael@0: { michael@0: HttpAsyncAborter::HandleAsyncAbort(); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHttpChannel::EnsureAssocReq() michael@0: { michael@0: // Confirm Assoc-Req response header on pipelined transactions michael@0: // per draft-nottingham-http-pipeline-01.txt michael@0: // of the form: GET http://blah.com/foo/bar?qv michael@0: // return NS_OK as long as we don't find a violation michael@0: // (i.e. no header is ok, as are malformed headers, as are michael@0: // transactions that have not been pipelined (unless those have been michael@0: // opted in via pragma)) michael@0: michael@0: if (!mResponseHead) michael@0: return NS_OK; michael@0: michael@0: const char *assoc_val = mResponseHead->PeekHeader(nsHttp::Assoc_Req); michael@0: if (!assoc_val) michael@0: return NS_OK; michael@0: michael@0: if (!mTransaction || !mURI) michael@0: return NS_OK; michael@0: michael@0: if (!mTransaction->PipelinePosition()) { michael@0: // "Pragma: X-Verify-Assoc-Req" can be used to verify even non pipelined michael@0: // transactions. It is used by test harness. michael@0: michael@0: const char *pragma_val = mResponseHead->PeekHeader(nsHttp::Pragma); michael@0: if (!pragma_val || michael@0: !nsHttp::FindToken(pragma_val, "X-Verify-Assoc-Req", michael@0: HTTP_HEADER_VALUE_SEPS)) michael@0: return NS_OK; michael@0: } michael@0: michael@0: char *method = net_FindCharNotInSet(assoc_val, HTTP_LWS); michael@0: if (!method) michael@0: return NS_OK; michael@0: michael@0: bool equals; michael@0: char *endofmethod; michael@0: michael@0: assoc_val = nullptr; michael@0: endofmethod = net_FindCharInSet(method, HTTP_LWS); michael@0: if (endofmethod) michael@0: assoc_val = net_FindCharNotInSet(endofmethod, HTTP_LWS); michael@0: if (!assoc_val) michael@0: return NS_OK; michael@0: michael@0: // check the method michael@0: int32_t methodlen = strlen(mRequestHead.Method().get()); michael@0: if ((methodlen != (endofmethod - method)) || michael@0: PL_strncmp(method, michael@0: mRequestHead.Method().get(), michael@0: endofmethod - method)) { michael@0: LOG((" Assoc-Req failure Method %s", method)); michael@0: if (mConnectionInfo) michael@0: gHttpHandler->ConnMgr()-> michael@0: PipelineFeedbackInfo(mConnectionInfo, michael@0: nsHttpConnectionMgr::RedCorruptedContent, michael@0: nullptr, 0); michael@0: michael@0: nsCOMPtr consoleService = michael@0: do_GetService(NS_CONSOLESERVICE_CONTRACTID); michael@0: if (consoleService) { michael@0: nsAutoString message michael@0: (NS_LITERAL_STRING("Failed Assoc-Req. Received ")); michael@0: AppendASCIItoUTF16( michael@0: mResponseHead->PeekHeader(nsHttp::Assoc_Req), michael@0: message); michael@0: message += NS_LITERAL_STRING(" expected method "); michael@0: AppendASCIItoUTF16(mRequestHead.Method().get(), message); michael@0: consoleService->LogStringMessage(message.get()); michael@0: } michael@0: michael@0: if (gHttpHandler->EnforceAssocReq()) michael@0: return NS_ERROR_CORRUPTED_CONTENT; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // check the URL michael@0: nsCOMPtr assoc_url; michael@0: if (NS_FAILED(NS_NewURI(getter_AddRefs(assoc_url), assoc_val)) || michael@0: !assoc_url) michael@0: return NS_OK; michael@0: michael@0: mURI->Equals(assoc_url, &equals); michael@0: if (!equals) { michael@0: LOG((" Assoc-Req failure URL %s", assoc_val)); michael@0: if (mConnectionInfo) michael@0: gHttpHandler->ConnMgr()-> michael@0: PipelineFeedbackInfo(mConnectionInfo, michael@0: nsHttpConnectionMgr::RedCorruptedContent, michael@0: nullptr, 0); michael@0: michael@0: nsCOMPtr consoleService = michael@0: do_GetService(NS_CONSOLESERVICE_CONTRACTID); michael@0: if (consoleService) { michael@0: nsAutoString message michael@0: (NS_LITERAL_STRING("Failed Assoc-Req. Received ")); michael@0: AppendASCIItoUTF16( michael@0: mResponseHead->PeekHeader(nsHttp::Assoc_Req), michael@0: message); michael@0: message += NS_LITERAL_STRING(" expected URL "); michael@0: AppendASCIItoUTF16(mSpec.get(), message); michael@0: consoleService->LogStringMessage(message.get()); michael@0: } michael@0: michael@0: if (gHttpHandler->EnforceAssocReq()) michael@0: return NS_ERROR_CORRUPTED_CONTENT; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: bool michael@0: nsHttpChannel::IsResumable(int64_t partialLen, int64_t contentLength, michael@0: bool ignoreMissingPartialLen) const michael@0: { michael@0: bool hasContentEncoding = michael@0: mCachedResponseHead->PeekHeader(nsHttp::Content_Encoding) michael@0: != nullptr; michael@0: michael@0: return (partialLen < contentLength) && michael@0: (partialLen > 0 || ignoreMissingPartialLen) && michael@0: !hasContentEncoding && michael@0: mCachedResponseHead->IsResumable() && michael@0: !mCustomConditionalRequest && michael@0: !mCachedResponseHead->NoStore(); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::MaybeSetupByteRangeRequest(int64_t partialLen, int64_t contentLength) michael@0: { michael@0: // Be pesimistic michael@0: mIsPartialRequest = false; michael@0: michael@0: if (!IsResumable(partialLen, contentLength)) michael@0: return NS_ERROR_NOT_RESUMABLE; michael@0: michael@0: // looks like a partial entry we can reuse; add If-Range michael@0: // and Range headers. michael@0: nsresult rv = SetupByteRangeRequest(partialLen); michael@0: if (NS_FAILED(rv)) { michael@0: // Make the request unconditional again. michael@0: mRequestHead.ClearHeader(nsHttp::Range); michael@0: mRequestHead.ClearHeader(nsHttp::If_Range); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::SetupByteRangeRequest(int64_t partialLen) michael@0: { michael@0: // cached content has been found to be partial, add necessary request michael@0: // headers to complete cache entry. michael@0: michael@0: // use strongest validator available... michael@0: const char *val = mCachedResponseHead->PeekHeader(nsHttp::ETag); michael@0: if (!val) michael@0: val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified); michael@0: if (!val) { michael@0: // if we hit this code it means mCachedResponseHead->IsResumable() is michael@0: // either broken or not being called. michael@0: NS_NOTREACHED("no cache validator"); michael@0: mIsPartialRequest = false; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: char buf[64]; michael@0: PR_snprintf(buf, sizeof(buf), "bytes=%lld-", partialLen); michael@0: michael@0: mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(buf)); michael@0: mRequestHead.SetHeader(nsHttp::If_Range, nsDependentCString(val)); michael@0: mIsPartialRequest = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ProcessPartialContent() michael@0: { michael@0: // ok, we've just received a 206 michael@0: // michael@0: // we need to stream whatever data is in the cache out first, and then michael@0: // pick up whatever data is on the wire, writing it into the cache. michael@0: michael@0: LOG(("nsHttpChannel::ProcessPartialContent [this=%p]\n", this)); michael@0: michael@0: NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED); michael@0: NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: // Make sure to clear bogus content-encodings before looking at the header michael@0: ClearBogusContentEncodingIfNeeded(); michael@0: michael@0: // Check if the content-encoding we now got is different from the one we michael@0: // got before michael@0: if (PL_strcasecmp(mResponseHead->PeekHeader(nsHttp::Content_Encoding), michael@0: mCachedResponseHead->PeekHeader(nsHttp::Content_Encoding)) michael@0: != 0) { michael@0: Cancel(NS_ERROR_INVALID_CONTENT_ENCODING); michael@0: return CallOnStartRequest(); michael@0: } michael@0: michael@0: nsresult rv; michael@0: michael@0: int64_t cachedContentLength = mCachedResponseHead->ContentLength(); michael@0: int64_t entitySize = mResponseHead->TotalEntitySize(); michael@0: michael@0: LOG(("nsHttpChannel::ProcessPartialContent [this=%p trans=%p] " michael@0: "original content-length %lld, entity-size %lld, content-range %s\n", michael@0: this, mTransaction.get(), cachedContentLength, entitySize, michael@0: mResponseHead->PeekHeader(nsHttp::Content_Range))); michael@0: michael@0: if ((entitySize >= 0) && (cachedContentLength >= 0) && michael@0: (entitySize != cachedContentLength)) { michael@0: LOG(("nsHttpChannel::ProcessPartialContent [this=%p] " michael@0: "206 has different total entity size than the content length " michael@0: "of the original partially cached entity.\n", this)); michael@0: michael@0: mCacheEntry->AsyncDoom(nullptr); michael@0: Cancel(NS_ERROR_CORRUPTED_CONTENT); michael@0: return CallOnStartRequest(); michael@0: } michael@0: michael@0: if (mConcurentCacheAccess) { michael@0: // We started to read cached data sooner than its write has been done. michael@0: // But the concurrent write has not finished completely, so we had to michael@0: // do a range request. Now let the content coming from the network michael@0: // be presented to consumers and also stored to the cache entry. michael@0: michael@0: rv = InstallCacheListener(mLogicalOffset); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (mOfflineCacheEntry) { michael@0: rv = InstallOfflineCacheListener(mLogicalOffset); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: // merge any new headers with the cached response headers michael@0: rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers()); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // update the cached response head michael@0: nsAutoCString head; michael@0: mCachedResponseHead->Flatten(head, true); michael@0: rv = mCacheEntry->SetMetaDataElement("response-head", head.get()); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: UpdateInhibitPersistentCachingFlag(); michael@0: michael@0: rv = UpdateExpirationTime(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mCachedContentIsPartial = false; michael@0: michael@0: // notify observers interested in looking at a response that has been michael@0: // merged with any cached headers (http-on-examine-merged-response). michael@0: gHttpHandler->OnExamineMergedResponse(this); michael@0: } michael@0: else { michael@0: // suspend the current transaction michael@0: rv = mTransactionPump->Suspend(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // merge any new headers with the cached response headers michael@0: rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers()); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // update the cached response head michael@0: nsAutoCString head; michael@0: mCachedResponseHead->Flatten(head, true); michael@0: rv = mCacheEntry->SetMetaDataElement("response-head", head.get()); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // make the cached response be the current response michael@0: mResponseHead = mCachedResponseHead; michael@0: michael@0: UpdateInhibitPersistentCachingFlag(); michael@0: michael@0: rv = UpdateExpirationTime(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // notify observers interested in looking at a response that has been michael@0: // merged with any cached headers (http-on-examine-merged-response). michael@0: gHttpHandler->OnExamineMergedResponse(this); michael@0: michael@0: // the cached content is valid, although incomplete. michael@0: mCachedContentIsValid = true; michael@0: rv = ReadFromCache(false); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::OnDoneReadingPartialCacheEntry(bool *streamDone) michael@0: { michael@0: nsresult rv; michael@0: michael@0: LOG(("nsHttpChannel::OnDoneReadingPartialCacheEntry [this=%p]", this)); michael@0: michael@0: // by default, assume we would have streamed all data or failed... michael@0: *streamDone = true; michael@0: michael@0: // setup cache listener to append to cache entry michael@0: int64_t size; michael@0: rv = mCacheEntry->GetDataSize(&size); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = InstallCacheListener(size); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Entry is valid, do it now, after the output stream has been opened, michael@0: // otherwise when done earlier, pending readers would consider the cache michael@0: // entry still as partial (CacheEntry::GetDataSize would return the partial michael@0: // data size) and consumers would do the conditional request again. michael@0: rv = mCacheEntry->SetValid(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // need to track the logical offset of the data being sent to our listener michael@0: mLogicalOffset = size; michael@0: michael@0: // we're now completing the cached content, so we can clear this flag. michael@0: // this puts us in the state of a regular download. michael@0: mCachedContentIsPartial = false; michael@0: michael@0: // resume the transaction if it exists, otherwise the pipe contained the michael@0: // remaining part of the document and we've now streamed all of the data. michael@0: if (mTransactionPump) { michael@0: rv = mTransactionPump->Resume(); michael@0: if (NS_SUCCEEDED(rv)) michael@0: *streamDone = false; michael@0: } michael@0: else michael@0: NS_NOTREACHED("no transaction"); michael@0: return rv; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: nsHttpChannel::ProcessNotModified() michael@0: { michael@0: nsresult rv; michael@0: michael@0: LOG(("nsHttpChannel::ProcessNotModified [this=%p]\n", this)); michael@0: michael@0: if (mCustomConditionalRequest) { michael@0: LOG(("Bypassing ProcessNotModified due to custom conditional headers")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (!mDidReval) { michael@0: LOG(("Server returned a 304 response even though we did not send a " michael@0: "conditional request")); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: MOZ_ASSERT(mCachedResponseHead); michael@0: MOZ_ASSERT(mCacheEntry); michael@0: NS_ENSURE_TRUE(mCachedResponseHead && mCacheEntry, NS_ERROR_UNEXPECTED); michael@0: michael@0: // If the 304 response contains a Last-Modified different than the michael@0: // one in our cache that is pretty suspicious and is, in at least the michael@0: // case of bug 716840, a sign of the server having previously corrupted michael@0: // our cache with a bad response. Take the minor step here of just dooming michael@0: // that cache entry so there is a fighting chance of getting things on the michael@0: // right track as well as disabling pipelining for that host. michael@0: michael@0: nsAutoCString lastModifiedCached; michael@0: nsAutoCString lastModified304; michael@0: michael@0: rv = mCachedResponseHead->GetHeader(nsHttp::Last_Modified, michael@0: lastModifiedCached); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = mResponseHead->GetHeader(nsHttp::Last_Modified, michael@0: lastModified304); michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv) && !lastModified304.Equals(lastModifiedCached)) { michael@0: LOG(("Cache Entry and 304 Last-Modified Headers Do Not Match " michael@0: "[%s] and [%s]\n", michael@0: lastModifiedCached.get(), lastModified304.get())); michael@0: michael@0: mCacheEntry->AsyncDoom(nullptr); michael@0: if (mConnectionInfo) michael@0: gHttpHandler->ConnMgr()-> michael@0: PipelineFeedbackInfo(mConnectionInfo, michael@0: nsHttpConnectionMgr::RedCorruptedContent, michael@0: nullptr, 0); michael@0: Telemetry::Accumulate(Telemetry::CACHE_LM_INCONSISTENT, true); michael@0: } michael@0: michael@0: // merge any new headers with the cached response headers michael@0: rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers()); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // update the cached response head michael@0: nsAutoCString head; michael@0: mCachedResponseHead->Flatten(head, true); michael@0: rv = mCacheEntry->SetMetaDataElement("response-head", head.get()); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // make the cached response be the current response michael@0: mResponseHead = mCachedResponseHead; michael@0: michael@0: UpdateInhibitPersistentCachingFlag(); michael@0: michael@0: rv = UpdateExpirationTime(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = AddCacheEntryHeaders(mCacheEntry); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // notify observers interested in looking at a reponse that has been michael@0: // merged with any cached headers michael@0: gHttpHandler->OnExamineMergedResponse(this); michael@0: michael@0: mCachedContentIsValid = true; michael@0: michael@0: // Tell other consumers the entry is OK to use michael@0: rv = mCacheEntry->SetValid(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = ReadFromCache(false); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mTransactionReplaced = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ProcessFallback(bool *waitingForRedirectCallback) michael@0: { michael@0: LOG(("nsHttpChannel::ProcessFallback [this=%p]\n", this)); michael@0: nsresult rv; michael@0: michael@0: *waitingForRedirectCallback = false; michael@0: mFallingBack = false; michael@0: michael@0: // At this point a load has failed (either due to network problems michael@0: // or an error returned on the server). Perform an application michael@0: // cache fallback if we have a URI to fall back to. michael@0: if (!mApplicationCache || mFallbackKey.IsEmpty() || mFallbackChannel) { michael@0: LOG((" choosing not to fallback [%p,%s,%d]", michael@0: mApplicationCache.get(), mFallbackKey.get(), mFallbackChannel)); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Make sure the fallback entry hasn't been marked as a foreign michael@0: // entry. michael@0: uint32_t fallbackEntryType; michael@0: rv = mApplicationCache->GetTypes(mFallbackKey, &fallbackEntryType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (fallbackEntryType & nsIApplicationCache::ITEM_FOREIGN) { michael@0: // This cache points to a fallback that refers to a different michael@0: // manifest. Refuse to fall back. michael@0: return NS_OK; michael@0: } michael@0: michael@0: MOZ_ASSERT(fallbackEntryType & nsIApplicationCache::ITEM_FALLBACK, michael@0: "Fallback entry not marked correctly!"); michael@0: michael@0: // Kill any offline cache entry, and disable offline caching for the michael@0: // fallback. michael@0: if (mOfflineCacheEntry) { michael@0: mOfflineCacheEntry->AsyncDoom(nullptr); michael@0: mOfflineCacheEntry = nullptr; michael@0: } michael@0: michael@0: mApplicationCacheForWrite = nullptr; michael@0: mOfflineCacheEntry = nullptr; michael@0: michael@0: // Close the current cache entry. michael@0: CloseCacheEntry(true); michael@0: michael@0: // Create a new channel to load the fallback entry. michael@0: nsRefPtr newChannel; michael@0: rv = gHttpHandler->NewChannel(mURI, getter_AddRefs(newChannel)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = SetupReplacementChannel(mURI, newChannel, true); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Make sure the new channel loads from the fallback key. michael@0: nsCOMPtr httpInternal = michael@0: do_QueryInterface(newChannel, &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = httpInternal->SetupFallbackChannel(mFallbackKey.get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // ... and fallbacks should only load from the cache. michael@0: uint32_t newLoadFlags = mLoadFlags | LOAD_REPLACE | LOAD_ONLY_FROM_CACHE; michael@0: rv = newChannel->SetLoadFlags(newLoadFlags); michael@0: michael@0: // Inform consumers about this fake redirect michael@0: mRedirectChannel = newChannel; michael@0: uint32_t redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL; michael@0: michael@0: PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback); michael@0: rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags); michael@0: michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = WaitForRedirectCallback(); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: AutoRedirectVetoNotifier notifier(this); michael@0: PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback); michael@0: return rv; michael@0: } michael@0: michael@0: // Indicate we are now waiting for the asynchronous redirect callback michael@0: // if all went OK. michael@0: *waitingForRedirectCallback = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ContinueProcessFallback(nsresult rv) michael@0: { michael@0: AutoRedirectVetoNotifier notifier(this); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: NS_PRECONDITION(mRedirectChannel, "No redirect channel?"); michael@0: michael@0: // Make sure to do this _after_ calling OnChannelRedirect michael@0: mRedirectChannel->SetOriginalURI(mOriginalURI); michael@0: michael@0: rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) { michael@0: Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD, michael@0: true); michael@0: } michael@0: michael@0: // close down this channel michael@0: Cancel(NS_BINDING_REDIRECTED); michael@0: michael@0: notifier.RedirectSucceeded(); michael@0: michael@0: ReleaseListeners(); michael@0: michael@0: mFallingBack = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Determines if a request is a byte range request for a subrange, michael@0: // i.e. is a byte range request, but not a 0- byte range request. michael@0: static bool michael@0: IsSubRangeRequest(nsHttpRequestHead &aRequestHead) michael@0: { michael@0: if (!aRequestHead.PeekHeader(nsHttp::Range)) michael@0: return false; michael@0: nsAutoCString byteRange; michael@0: aRequestHead.GetHeader(nsHttp::Range, byteRange); michael@0: return !byteRange.EqualsLiteral("bytes=0-"); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::OpenCacheEntry(bool usingSSL) michael@0: { michael@0: MOZ_EVENT_TRACER_EXEC(this, "net::http::OpenCacheEntry"); michael@0: michael@0: // Handle correctly mCacheEntriesToWaitFor michael@0: AutoCacheWaitFlags waitFlags(this); michael@0: michael@0: // Drop this flag here michael@0: mConcurentCacheAccess = 0; michael@0: michael@0: nsresult rv; michael@0: michael@0: mLoadedFromApplicationCache = false; michael@0: mHasQueryString = HasQueryString(mRequestHead.ParsedMethod(), mURI); michael@0: michael@0: LOG(("nsHttpChannel::OpenCacheEntry [this=%p]", this)); michael@0: michael@0: // make sure we're not abusing this function michael@0: NS_PRECONDITION(!mCacheEntry, "cache entry already open"); michael@0: michael@0: nsAutoCString cacheKey; michael@0: michael@0: if (mRequestHead.IsPost()) { michael@0: // If the post id is already set then this is an attempt to replay michael@0: // a post transaction via the cache. Otherwise, we need a unique michael@0: // post id for this transaction. michael@0: if (mPostID == 0) michael@0: mPostID = gHttpHandler->GenerateUniqueID(); michael@0: } michael@0: else if (!mRequestHead.IsGet() && !mRequestHead.IsHead()) { michael@0: // don't use the cache for other types of requests michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mResuming) { michael@0: // We don't support caching for requests initiated michael@0: // via nsIResumableChannel. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Don't cache byte range requests which are subranges, only cache 0- michael@0: // byte range requests. michael@0: if (IsSubRangeRequest(mRequestHead)) michael@0: return NS_OK; michael@0: michael@0: // Pick up an application cache from the notification michael@0: // callbacks if available michael@0: if (!mApplicationCache && mInheritApplicationCache) { michael@0: nsCOMPtr appCacheContainer; michael@0: GetCallback(appCacheContainer); michael@0: michael@0: if (appCacheContainer) { michael@0: appCacheContainer->GetApplicationCache(getter_AddRefs(mApplicationCache)); michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr cacheStorageService = michael@0: do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsRefPtr info = GetLoadContextInfo(this); michael@0: nsCOMPtr cacheStorage; michael@0: nsCOMPtr openURI; michael@0: michael@0: /* Obtain optional third party isolation domain */ michael@0: nsAutoCString cacheDomain; michael@0: nsCOMPtr firstPartyIsolationURI; michael@0: nsCOMPtr thirdPartySvc michael@0: = do_GetService(THIRDPARTYUTIL_CONTRACTID); michael@0: rv = thirdPartySvc->GetFirstPartyIsolationURI(this, nullptr, michael@0: getter_AddRefs(firstPartyIsolationURI)); michael@0: if (NS_SUCCEEDED(rv) && firstPartyIsolationURI) { michael@0: thirdPartySvc->GetFirstPartyHostForIsolation(firstPartyIsolationURI, michael@0: cacheDomain); michael@0: } michael@0: michael@0: if (!mFallbackKey.IsEmpty() && mFallbackChannel) { michael@0: // This is a fallback channel, open fallback URI instead michael@0: rv = NS_NewURI(getter_AddRefs(openURI), mFallbackKey); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else { michael@0: openURI = mURI; michael@0: } michael@0: michael@0: uint32_t cacheEntryOpenFlags; michael@0: bool offline = gIOService->IsOffline(); michael@0: if (offline || (mLoadFlags & INHIBIT_CACHING)) { michael@0: if (BYPASS_LOCAL_CACHE(mLoadFlags) && !offline) { michael@0: goto bypassCacheEntryOpen; michael@0: } michael@0: cacheEntryOpenFlags = nsICacheStorage::OPEN_READONLY; michael@0: mCacheEntryIsReadOnly = true; michael@0: } michael@0: else if (BYPASS_LOCAL_CACHE(mLoadFlags) && !mApplicationCache) { michael@0: cacheEntryOpenFlags = nsICacheStorage::OPEN_TRUNCATE; michael@0: } michael@0: else { michael@0: cacheEntryOpenFlags = nsICacheStorage::OPEN_NORMALLY michael@0: | nsICacheStorage::CHECK_MULTITHREADED; michael@0: } michael@0: michael@0: if (mApplicationCache) { michael@0: rv = cacheStorageService->AppCacheStorage(info, michael@0: mApplicationCache, michael@0: getter_AddRefs(cacheStorage)); michael@0: } michael@0: else if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) { michael@0: rv = cacheStorageService->MemoryCacheStorage(info, // ? choose app cache as well... michael@0: getter_AddRefs(cacheStorage)); michael@0: } michael@0: else { michael@0: rv = cacheStorageService->DiskCacheStorage(info, michael@0: mChooseApplicationCache || (mLoadFlags & LOAD_CHECK_OFFLINE_CACHE), michael@0: getter_AddRefs(cacheStorage)); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Don't consider mLoadUnblocked here, since it's not indication of a demand michael@0: // to load prioritly. It's mostly used to load XHR requests, but those should michael@0: // not be considered as influencing the page load performance. michael@0: if (mLoadAsBlocking || (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI)) michael@0: cacheEntryOpenFlags |= nsICacheStorage::OPEN_PRIORITY; michael@0: michael@0: // Only for backward compatibility with the old cache back end. michael@0: // When removed, remove the flags and related code snippets. michael@0: if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) michael@0: cacheEntryOpenFlags |= nsICacheStorage::OPEN_BYPASS_IF_BUSY; michael@0: michael@0: rv = cacheStorage->AsyncOpenURI( michael@0: openURI, nsPrintfCString("%s@%d", cacheDomain.get(), mPostID), michael@0: cacheEntryOpenFlags, this); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: waitFlags.Keep(WAIT_FOR_CACHE_ENTRY); michael@0: michael@0: bypassCacheEntryOpen: michael@0: if (!mApplicationCacheForWrite) michael@0: return NS_OK; michael@0: michael@0: // If there is an app cache to write to, open the entry right now in parallel. michael@0: michael@0: // make sure we're not abusing this function michael@0: NS_PRECONDITION(!mOfflineCacheEntry, "cache entry already open"); michael@0: michael@0: if (offline) { michael@0: // only put things in the offline cache while online michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mLoadFlags & INHIBIT_CACHING) { michael@0: // respect demand not to cache michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!mRequestHead.IsGet()) { michael@0: // only cache complete documents offline michael@0: return NS_OK; michael@0: } michael@0: michael@0: rv = cacheStorageService->AppCacheStorage(info, mApplicationCacheForWrite, michael@0: getter_AddRefs(cacheStorage)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = cacheStorage->AsyncOpenURI( michael@0: mURI, cacheDomain, nsICacheStorage::OPEN_TRUNCATE, this); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: waitFlags.Keep(WAIT_FOR_OFFLINE_CACHE_ENTRY); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::CheckPartial(nsICacheEntry* aEntry, int64_t *aSize, int64_t *aContentLength) michael@0: { michael@0: nsresult rv; michael@0: michael@0: rv = aEntry->GetDataSize(aSize); michael@0: michael@0: if (NS_ERROR_IN_PROGRESS == rv) { michael@0: *aSize = -1; michael@0: rv = NS_OK; michael@0: } michael@0: michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsHttpResponseHead* responseHead = mCachedResponseHead michael@0: ? mCachedResponseHead michael@0: : mResponseHead; michael@0: michael@0: if (!responseHead) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: *aContentLength = responseHead->ContentLength(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appCache, michael@0: uint32_t* aResult) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: LOG(("nsHttpChannel::OnCacheEntryCheck enter [channel=%p entry=%p]", michael@0: this, entry)); michael@0: michael@0: // Remember the request is a custom conditional request so that we can michael@0: // process any 304 response correctly. michael@0: mCustomConditionalRequest = michael@0: mRequestHead.PeekHeader(nsHttp::If_Modified_Since) || michael@0: mRequestHead.PeekHeader(nsHttp::If_None_Match) || michael@0: mRequestHead.PeekHeader(nsHttp::If_Unmodified_Since) || michael@0: mRequestHead.PeekHeader(nsHttp::If_Match) || michael@0: mRequestHead.PeekHeader(nsHttp::If_Range); michael@0: michael@0: // Be pessimistic: assume the cache entry has no useful data. michael@0: *aResult = ENTRY_WANTED; michael@0: mCachedContentIsValid = false; michael@0: michael@0: nsXPIDLCString buf; michael@0: michael@0: // Get the method that was used to generate the cached response michael@0: rv = entry->GetMetaDataElement("request-method", getter_Copies(buf)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: bool methodWasHead = buf.EqualsLiteral("HEAD"); michael@0: bool methodWasGet = buf.EqualsLiteral("GET"); michael@0: michael@0: if (methodWasHead) { michael@0: // The cached response does not contain an entity. We can only reuse michael@0: // the response if the current request is also HEAD. michael@0: if (!mRequestHead.IsHead()) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: buf.Adopt(0); michael@0: michael@0: // We'll need this value in later computations... michael@0: uint32_t lastModifiedTime; michael@0: rv = entry->GetLastModified(&lastModifiedTime); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Determine if this is the first time that this cache entry michael@0: // has been accessed during this session. michael@0: bool fromPreviousSession = michael@0: (gHttpHandler->SessionStartTime() > lastModifiedTime); michael@0: michael@0: // Get the cached HTTP response headers michael@0: rv = entry->GetMetaDataElement("response-head", getter_Copies(buf)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Parse the cached HTTP response headers michael@0: mCachedResponseHead = new nsHttpResponseHead(); michael@0: rv = mCachedResponseHead->Parse((char *) buf.get()); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: buf.Adopt(0); michael@0: michael@0: bool isCachedRedirect = WillRedirect(mCachedResponseHead); michael@0: michael@0: // Do not return 304 responses from the cache, and also do not return michael@0: // any other non-redirect 3xx responses from the cache (see bug 759043). michael@0: NS_ENSURE_TRUE((mCachedResponseHead->Status() / 100 != 3) || michael@0: isCachedRedirect, NS_ERROR_ABORT); michael@0: michael@0: // Don't bother to validate items that are read-only, michael@0: // unless they are read-only because of INHIBIT_CACHING or because michael@0: // we're updating the offline cache. michael@0: // Don't bother to validate if this is a fallback entry. michael@0: if (!mApplicationCacheForWrite && michael@0: (appCache || michael@0: (mCacheEntryIsReadOnly && !(mLoadFlags & nsIRequest::INHIBIT_CACHING)) || michael@0: mFallbackChannel)) { michael@0: rv = OpenCacheInputStream(entry, true); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mCachedContentIsValid = true; michael@0: entry->MaybeMarkValid(); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: bool wantCompleteEntry = false; michael@0: michael@0: if (!methodWasHead && !isCachedRedirect) { michael@0: // If the cached content-length is set and it does not match the data michael@0: // size of the cached content, then the cached response is partial... michael@0: // either we need to issue a byte range request or we need to refetch michael@0: // the entire document. michael@0: // michael@0: // We exclude redirects from this check because we (usually) strip the michael@0: // entity when we store the cache entry, and even if we didn't, we michael@0: // always ignore a cached redirect's entity anyway. See bug 759043. michael@0: int64_t size, contentLength; michael@0: rv = CheckPartial(entry, &size, &contentLength); michael@0: NS_ENSURE_SUCCESS(rv,rv); michael@0: michael@0: if (size == int64_t(-1)) { michael@0: LOG((" write is in progress")); michael@0: if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) { michael@0: LOG((" not interested in the entry, " michael@0: "LOAD_BYPASS_LOCAL_CACHE_IF_BUSY specified")); michael@0: michael@0: *aResult = ENTRY_NOT_WANTED; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Ignore !(size > 0) from the resumability condition michael@0: if (!IsResumable(size, contentLength, true)) { michael@0: LOG((" wait for entry completion, " michael@0: "response is not resumable")); michael@0: michael@0: wantCompleteEntry = true; michael@0: } michael@0: else { michael@0: mConcurentCacheAccess = 1; michael@0: } michael@0: } michael@0: else if (contentLength != int64_t(-1) && contentLength != size) { michael@0: LOG(("Cached data size does not match the Content-Length header " michael@0: "[content-length=%lld size=%lld]\n", contentLength, size)); michael@0: michael@0: rv = MaybeSetupByteRangeRequest(size, contentLength); michael@0: mCachedContentIsPartial = NS_SUCCEEDED(rv) && mIsPartialRequest; michael@0: if (mCachedContentIsPartial) { michael@0: rv = OpenCacheInputStream(entry, false); michael@0: *aResult = ENTRY_NEEDS_REVALIDATION; michael@0: } michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: bool usingSSL = false; michael@0: rv = mURI->SchemeIs("https", &usingSSL); michael@0: NS_ENSURE_SUCCESS(rv,rv); michael@0: michael@0: bool doValidation = false; michael@0: bool canAddImsHeader = true; michael@0: michael@0: // Cached entry is not the entity we request (see bug #633743) michael@0: if (ResponseWouldVary(entry)) { michael@0: LOG(("Validating based on Vary headers returning TRUE\n")); michael@0: canAddImsHeader = false; michael@0: doValidation = true; michael@0: } michael@0: // If the LOAD_FROM_CACHE flag is set, any cached data can simply be used michael@0: else if (mLoadFlags & nsIRequest::LOAD_FROM_CACHE) { michael@0: LOG(("NOT validating based on LOAD_FROM_CACHE load flag\n")); michael@0: doValidation = false; michael@0: } michael@0: // If the VALIDATE_ALWAYS flag is set, any cached data won't be used until michael@0: // it's revalidated with the server. michael@0: else if (mLoadFlags & nsIRequest::VALIDATE_ALWAYS) { michael@0: LOG(("Validating based on VALIDATE_ALWAYS load flag\n")); michael@0: doValidation = true; michael@0: } michael@0: // Even if the VALIDATE_NEVER flag is set, there are still some cases in michael@0: // which we must validate the cached response with the server. michael@0: else if (mLoadFlags & nsIRequest::VALIDATE_NEVER) { michael@0: LOG(("VALIDATE_NEVER set\n")); michael@0: // if no-store or if no-cache and ssl, validate cached response (see michael@0: // bug 112564 for an explanation of this logic) michael@0: if (mCachedResponseHead->NoStore() || michael@0: (mCachedResponseHead->NoCache() && usingSSL)) { michael@0: LOG(("Validating based on (no-store || (no-cache && ssl)) logic\n")); michael@0: doValidation = true; michael@0: } michael@0: else { michael@0: LOG(("NOT validating based on VALIDATE_NEVER load flag\n")); michael@0: doValidation = false; michael@0: } michael@0: } michael@0: // check if validation is strictly required... michael@0: else if (mCachedResponseHead->MustValidate()) { michael@0: LOG(("Validating based on MustValidate() returning TRUE\n")); michael@0: doValidation = true; michael@0: } michael@0: else if (MustValidateBasedOnQueryUrl()) { michael@0: LOG(("Validating based on RFC 2616 section 13.9 " michael@0: "(query-url w/o explicit expiration-time)\n")); michael@0: doValidation = true; michael@0: } michael@0: // Check if the cache entry has expired... michael@0: else { michael@0: uint32_t time = 0; // a temporary variable for storing time values... michael@0: michael@0: rv = entry->GetExpirationTime(&time); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: LOG((" NowInSeconds()=%u, time=%u", NowInSeconds(), time)); michael@0: if (NowInSeconds() <= time) michael@0: doValidation = false; michael@0: else if (mCachedResponseHead->MustValidateIfExpired()) michael@0: doValidation = true; michael@0: else if (mLoadFlags & nsIRequest::VALIDATE_ONCE_PER_SESSION) { michael@0: // If the cached response does not include expiration infor- michael@0: // mation, then we must validate the response, despite whether michael@0: // or not this is the first access this session. This behavior michael@0: // is consistent with existing browsers and is generally expected michael@0: // by web authors. michael@0: rv = mCachedResponseHead->ComputeFreshnessLifetime(&time); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (time == 0) michael@0: doValidation = true; michael@0: else michael@0: doValidation = fromPreviousSession; michael@0: } michael@0: else michael@0: doValidation = true; michael@0: michael@0: LOG(("%salidating based on expiration time\n", doValidation ? "V" : "Not v")); michael@0: } michael@0: michael@0: if (!doValidation && mRequestHead.PeekHeader(nsHttp::If_Match) && michael@0: (methodWasGet || methodWasHead)) { michael@0: const char *requestedETag, *cachedETag; michael@0: cachedETag = mCachedResponseHead->PeekHeader(nsHttp::ETag); michael@0: requestedETag = mRequestHead.PeekHeader(nsHttp::If_Match); michael@0: if (cachedETag && (!strncmp(cachedETag, "W/", 2) || michael@0: strcmp(requestedETag, cachedETag))) { michael@0: // User has defined If-Match header, if the cached entry is not michael@0: // matching the provided header value or the cached ETag is weak, michael@0: // force validation. michael@0: doValidation = true; michael@0: } michael@0: } michael@0: michael@0: if (!doValidation) { michael@0: // michael@0: // Check the authorization headers used to generate the cache entry. michael@0: // We must validate the cache entry if: michael@0: // michael@0: // 1) the cache entry was generated prior to this session w/ michael@0: // credentials (see bug 103402). michael@0: // 2) the cache entry was generated w/o credentials, but would now michael@0: // require credentials (see bug 96705). michael@0: // michael@0: // NOTE: this does not apply to proxy authentication. michael@0: // michael@0: entry->GetMetaDataElement("auth", getter_Copies(buf)); michael@0: doValidation = michael@0: (fromPreviousSession && !buf.IsEmpty()) || michael@0: (buf.IsEmpty() && mRequestHead.PeekHeader(nsHttp::Authorization)); michael@0: } michael@0: michael@0: // Bug #561276: We maintain a chain of cache-keys which returns cached michael@0: // 3xx-responses (redirects) in order to detect cycles. If a cycle is michael@0: // found, ignore the cached response and hit the net. Otherwise, use michael@0: // the cached response and add the cache-key to the chain. Note that michael@0: // a limited number of redirects (cached or not) is allowed and is michael@0: // enforced independently of this mechanism michael@0: if (!doValidation && isCachedRedirect) { michael@0: nsAutoCString cacheKey; michael@0: GenerateCacheKey(mPostID, cacheKey); michael@0: michael@0: if (!mRedirectedCachekeys) michael@0: mRedirectedCachekeys = new nsTArray(); michael@0: else if (mRedirectedCachekeys->Contains(cacheKey)) michael@0: doValidation = true; michael@0: michael@0: LOG(("Redirection-chain %s key %s\n", michael@0: doValidation ? "contains" : "does not contain", cacheKey.get())); michael@0: michael@0: // Append cacheKey if not in the chain already michael@0: if (!doValidation) michael@0: mRedirectedCachekeys->AppendElement(cacheKey); michael@0: } michael@0: michael@0: mCachedContentIsValid = !doValidation; michael@0: michael@0: if (doValidation) { michael@0: // michael@0: // now, we are definitely going to issue a HTTP request to the server. michael@0: // make it conditional if possible. michael@0: // michael@0: // do not attempt to validate no-store content, since servers will not michael@0: // expect it to be cached. (we only keep it in our cache for the michael@0: // purposes of back/forward, etc.) michael@0: // michael@0: // the request method MUST be either GET or HEAD (see bug 175641). michael@0: // michael@0: // do not override conditional headers when consumer has defined its own michael@0: if (!mCachedResponseHead->NoStore() && michael@0: (mRequestHead.IsGet() || mRequestHead.IsHead()) && michael@0: !mCustomConditionalRequest) { michael@0: michael@0: if (mConcurentCacheAccess) { michael@0: // In case of concurrent read and also validation request we michael@0: // must wait for the current writer to close the output stream michael@0: // first. Otherwise, when the writer's job would have been interrupted michael@0: // before all the data were downloaded, we'd have to do a range request michael@0: // which would be a second request in line during this channel's michael@0: // life-time. nsHttpChannel is not designed to do that, so rather michael@0: // turn off concurrent read and wait for entry's completion. michael@0: // Then only re-validation or range-re-validation request will go out. michael@0: mConcurentCacheAccess = 0; michael@0: // This will cause that OnCacheEntryCheck is called again with the same michael@0: // entry after the writer is done. michael@0: wantCompleteEntry = true; michael@0: } else { michael@0: const char *val; michael@0: // Add If-Modified-Since header if a Last-Modified was given michael@0: // and we are allowed to do this (see bugs 510359 and 269303) michael@0: if (canAddImsHeader) { michael@0: val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified); michael@0: if (val) michael@0: mRequestHead.SetHeader(nsHttp::If_Modified_Since, michael@0: nsDependentCString(val)); michael@0: } michael@0: // Add If-None-Match header if an ETag was given in the response michael@0: val = mCachedResponseHead->PeekHeader(nsHttp::ETag); michael@0: if (val) michael@0: mRequestHead.SetHeader(nsHttp::If_None_Match, michael@0: nsDependentCString(val)); michael@0: mDidReval = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (mCachedContentIsValid || mDidReval) { michael@0: rv = OpenCacheInputStream(entry, mCachedContentIsValid); michael@0: if (NS_FAILED(rv)) { michael@0: // If we can't get the entity then we have to act as though we michael@0: // don't have the cache entry. michael@0: if (mDidReval) { michael@0: // Make the request unconditional again. michael@0: mRequestHead.ClearHeader(nsHttp::If_Modified_Since); michael@0: mRequestHead.ClearHeader(nsHttp::If_None_Match); michael@0: mRequestHead.ClearHeader(nsHttp::ETag); michael@0: mDidReval = false; michael@0: } michael@0: mCachedContentIsValid = false; michael@0: } michael@0: } michael@0: michael@0: if (mDidReval) michael@0: *aResult = ENTRY_NEEDS_REVALIDATION; michael@0: else if (wantCompleteEntry) michael@0: *aResult = RECHECK_AFTER_WRITE_FINISHED; michael@0: else michael@0: *aResult = ENTRY_WANTED; michael@0: michael@0: if (mCachedContentIsValid) { michael@0: entry->MaybeMarkValid(); michael@0: } michael@0: michael@0: LOG(("nsHTTPChannel::OnCacheEntryCheck exit [this=%p doValidation=%d result=%d]\n", michael@0: this, doValidation, *aResult)); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::OnCacheEntryAvailable(nsICacheEntry *entry, michael@0: bool aNew, michael@0: nsIApplicationCache* aAppCache, michael@0: nsresult status) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: nsresult rv; michael@0: michael@0: LOG(("nsHttpChannel::OnCacheEntryAvailable [this=%p entry=%p " michael@0: "new=%d appcache=%p status=%x]\n", this, entry, aNew, aAppCache, status)); michael@0: michael@0: // if the channel's already fired onStopRequest, then we should ignore michael@0: // this event. michael@0: if (!mIsPending) { michael@0: mCacheInputStream.CloseAndRelease(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: rv = OnCacheEntryAvailableInternal(entry, aNew, aAppCache, status); michael@0: if (NS_FAILED(rv)) { michael@0: CloseCacheEntry(true); michael@0: AsyncAbort(rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::OnCacheEntryAvailableInternal(nsICacheEntry *entry, michael@0: bool aNew, michael@0: nsIApplicationCache* aAppCache, michael@0: nsresult status) michael@0: { michael@0: nsresult rv; michael@0: michael@0: if (mCanceled) { michael@0: LOG(("channel was canceled [this=%p status=%x]\n", this, mStatus)); michael@0: return mStatus; michael@0: } michael@0: michael@0: if (aAppCache) { michael@0: if (mApplicationCache == aAppCache && !mCacheEntry) { michael@0: rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status); michael@0: } michael@0: else if (mApplicationCacheForWrite == aAppCache && aNew && !mOfflineCacheEntry) { michael@0: rv = OnOfflineCacheEntryForWritingAvailable(entry, aAppCache, status); michael@0: } michael@0: else { michael@0: rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status); michael@0: } michael@0: } michael@0: else { michael@0: rv = OnNormalCacheEntryAvailable(entry, aNew, status); michael@0: } michael@0: michael@0: if (NS_FAILED(rv) && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) { michael@0: // If we have a fallback URI (and we're not already michael@0: // falling back), process the fallback asynchronously. michael@0: if (!mFallbackChannel && !mFallbackKey.IsEmpty()) { michael@0: return AsyncCall(&nsHttpChannel::HandleAsyncFallback); michael@0: } michael@0: return NS_ERROR_DOCUMENT_NOT_CACHED; michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // We may be waiting for more callbacks... michael@0: if (mCacheEntriesToWaitFor) michael@0: return NS_OK; michael@0: michael@0: return ContinueConnect(); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntry *aEntry, michael@0: bool aNew, michael@0: nsresult aEntryStatus) michael@0: { michael@0: mCacheEntriesToWaitFor &= ~WAIT_FOR_CACHE_ENTRY; michael@0: michael@0: if (NS_FAILED(aEntryStatus) || aNew) { michael@0: // Make sure this flag is dropped. It may happen the entry is doomed michael@0: // between OnCacheEntryCheck and OnCacheEntryAvailable. michael@0: mCachedContentIsValid = false; michael@0: michael@0: if (mLoadFlags & LOAD_ONLY_FROM_CACHE) { michael@0: // if this channel is only allowed to pull from the cache, then michael@0: // we must fail if we were unable to open a cache entry for read. michael@0: return NS_ERROR_DOCUMENT_NOT_CACHED; michael@0: } michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(aEntryStatus)) { michael@0: mCacheEntry = aEntry; michael@0: mCacheEntryIsWriteOnly = aNew; michael@0: michael@0: if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) { michael@0: Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD, michael@0: false); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::OnOfflineCacheEntryAvailable(nsICacheEntry *aEntry, michael@0: bool aNew, michael@0: nsIApplicationCache* aAppCache, michael@0: nsresult aEntryStatus) michael@0: { michael@0: MOZ_ASSERT(!mApplicationCache || aAppCache == mApplicationCache); michael@0: MOZ_ASSERT(!aNew || !aEntry || mApplicationCacheForWrite); michael@0: michael@0: mCacheEntriesToWaitFor &= ~WAIT_FOR_CACHE_ENTRY; michael@0: michael@0: nsresult rv; michael@0: michael@0: if (!mApplicationCache) michael@0: mApplicationCache = aAppCache; michael@0: michael@0: if (NS_SUCCEEDED(aEntryStatus)) { michael@0: // We successfully opened an offline cache session and the entry, michael@0: // so indicate we will load from the offline cache. michael@0: mLoadedFromApplicationCache = true; michael@0: mCacheEntryIsReadOnly = true; michael@0: mCacheEntry = aEntry; michael@0: mCacheEntryIsWriteOnly = false; michael@0: michael@0: if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI && !mApplicationCacheForWrite) { michael@0: Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD, michael@0: true); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!mApplicationCacheForWrite && !mFallbackChannel) { michael@0: // Check for namespace match. michael@0: nsCOMPtr namespaceEntry; michael@0: rv = mApplicationCache->GetMatchingNamespace(mSpec, michael@0: getter_AddRefs(namespaceEntry)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t namespaceType = 0; michael@0: if (!namespaceEntry || michael@0: NS_FAILED(namespaceEntry->GetItemType(&namespaceType)) || michael@0: (namespaceType & michael@0: (nsIApplicationCacheNamespace::NAMESPACE_FALLBACK | michael@0: nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) == 0) { michael@0: // When loading from an application cache, only items michael@0: // on the whitelist or matching a michael@0: // fallback namespace should hit the network... michael@0: mLoadFlags |= LOAD_ONLY_FROM_CACHE; michael@0: michael@0: // ... and if there were an application cache entry, michael@0: // we would have found it earlier. michael@0: return NS_ERROR_CACHE_KEY_NOT_FOUND; michael@0: } michael@0: michael@0: if (namespaceType & michael@0: nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) { michael@0: rv = namespaceEntry->GetData(mFallbackKey); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::OnOfflineCacheEntryForWritingAvailable(nsICacheEntry *aEntry, michael@0: nsIApplicationCache* aAppCache, michael@0: nsresult aEntryStatus) michael@0: { michael@0: MOZ_ASSERT(mApplicationCacheForWrite && aAppCache == mApplicationCacheForWrite); michael@0: michael@0: mCacheEntriesToWaitFor &= ~WAIT_FOR_OFFLINE_CACHE_ENTRY; michael@0: michael@0: if (NS_SUCCEEDED(aEntryStatus)) { michael@0: mOfflineCacheEntry = aEntry; michael@0: if (NS_FAILED(aEntry->GetLastModified(&mOfflineCacheLastModifiedTime))) { michael@0: mOfflineCacheLastModifiedTime = 0; michael@0: } michael@0: } michael@0: michael@0: return aEntryStatus; michael@0: } michael@0: michael@0: // Generates the proper cache-key for this instance of nsHttpChannel michael@0: nsresult michael@0: nsHttpChannel::GenerateCacheKey(uint32_t postID, nsACString &cacheKey) michael@0: { michael@0: AssembleCacheKey(mFallbackChannel ? mFallbackKey.get() : mSpec.get(), michael@0: postID, cacheKey); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Assembles a cache-key from the given pieces of information and |mLoadFlags| michael@0: void michael@0: nsHttpChannel::AssembleCacheKey(const char *spec, uint32_t postID, michael@0: nsACString &cacheKey) michael@0: { michael@0: cacheKey.Truncate(); michael@0: michael@0: if (mLoadFlags & LOAD_ANONYMOUS) { michael@0: cacheKey.AssignLiteral("anon&"); michael@0: } michael@0: michael@0: if (postID) { michael@0: char buf[32]; michael@0: PR_snprintf(buf, sizeof(buf), "id=%x&", postID); michael@0: cacheKey.Append(buf); michael@0: } michael@0: michael@0: if (strlen(mCacheDomain.get()) > 0) { michael@0: cacheKey.AppendLiteral("domain="); michael@0: cacheKey.Append(mCacheDomain.get()); michael@0: cacheKey.AppendLiteral("&"); michael@0: } michael@0: michael@0: if (!cacheKey.IsEmpty()) { michael@0: cacheKey.AppendLiteral("uri="); michael@0: } michael@0: michael@0: // Strip any trailing #ref from the URL before using it as the key michael@0: const char *p = strchr(spec, '#'); michael@0: if (p) michael@0: cacheKey.Append(spec, p - spec); michael@0: else michael@0: cacheKey.Append(spec); michael@0: } michael@0: michael@0: // UpdateExpirationTime is called when a new response comes in from the server. michael@0: // It updates the stored response-time and sets the expiration time on the michael@0: // cache entry. michael@0: // michael@0: // From section 13.2.4 of RFC2616, we compute expiration time as follows: michael@0: // michael@0: // timeRemaining = freshnessLifetime - currentAge michael@0: // expirationTime = now + timeRemaining michael@0: // michael@0: nsresult michael@0: nsHttpChannel::UpdateExpirationTime() michael@0: { michael@0: NS_ENSURE_TRUE(mResponseHead, NS_ERROR_FAILURE); michael@0: michael@0: nsresult rv; michael@0: michael@0: uint32_t expirationTime = 0; michael@0: if (!mResponseHead->MustValidate()) { michael@0: uint32_t freshnessLifetime = 0; michael@0: michael@0: rv = mResponseHead->ComputeFreshnessLifetime(&freshnessLifetime); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (freshnessLifetime > 0) { michael@0: uint32_t now = NowInSeconds(), currentAge = 0; michael@0: michael@0: rv = mResponseHead->ComputeCurrentAge(now, mRequestTime, ¤tAge); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: LOG(("freshnessLifetime = %u, currentAge = %u\n", michael@0: freshnessLifetime, currentAge)); michael@0: michael@0: if (freshnessLifetime > currentAge) { michael@0: uint32_t timeRemaining = freshnessLifetime - currentAge; michael@0: // be careful... now + timeRemaining may overflow michael@0: if (now + timeRemaining < now) michael@0: expirationTime = uint32_t(-1); michael@0: else michael@0: expirationTime = now + timeRemaining; michael@0: } michael@0: else michael@0: expirationTime = now; michael@0: } michael@0: } michael@0: michael@0: rv = mCacheEntry->SetExpirationTime(expirationTime); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (mOfflineCacheEntry) { michael@0: rv = mOfflineCacheEntry->SetExpirationTime(expirationTime); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /*static*/ inline bool michael@0: nsHttpChannel::HasQueryString(nsHttpRequestHead::ParsedMethodType method, nsIURI * uri) michael@0: { michael@0: // Must be called on the main thread because nsIURI does not implement michael@0: // thread-safe QueryInterface. michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: if (method != nsHttpRequestHead::kMethod_Get && michael@0: method != nsHttpRequestHead::kMethod_Head) michael@0: return false; michael@0: michael@0: nsAutoCString query; michael@0: nsCOMPtr url = do_QueryInterface(uri); michael@0: nsresult rv = url->GetQuery(query); michael@0: return NS_SUCCEEDED(rv) && !query.IsEmpty(); michael@0: } michael@0: michael@0: bool michael@0: nsHttpChannel::MustValidateBasedOnQueryUrl() const michael@0: { michael@0: // RFC 2616, section 13.9 states that GET-requests with a query-url michael@0: // MUST NOT be treated as fresh unless the server explicitly provides michael@0: // an expiration-time in the response. See bug #468594 michael@0: // Section 13.2.1 (6th paragraph) defines "explicit expiration time" michael@0: if (mHasQueryString) michael@0: { michael@0: uint32_t tmp; // we don't need the value, just whether it's set michael@0: nsresult rv = mCachedResponseHead->GetExpiresValue(&tmp); michael@0: if (NS_FAILED(rv)) { michael@0: rv = mCachedResponseHead->GetMaxAgeValue(&tmp); michael@0: if (NS_FAILED(rv)) { michael@0: return true; michael@0: } michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: michael@0: bool michael@0: nsHttpChannel::ShouldUpdateOfflineCacheEntry() michael@0: { michael@0: if (!mApplicationCacheForWrite || !mOfflineCacheEntry) { michael@0: return false; michael@0: } michael@0: michael@0: // if we're updating the cache entry, update the offline cache entry too michael@0: if (mCacheEntry && mCacheEntryIsWriteOnly) { michael@0: return true; michael@0: } michael@0: michael@0: // if there's nothing in the offline cache, add it michael@0: if (mOfflineCacheEntry) { michael@0: return true; michael@0: } michael@0: michael@0: // if the document is newer than the offline entry, update it michael@0: uint32_t docLastModifiedTime; michael@0: nsresult rv = mResponseHead->GetLastModifiedValue(&docLastModifiedTime); michael@0: if (NS_FAILED(rv)) { michael@0: return true; michael@0: } michael@0: michael@0: if (mOfflineCacheLastModifiedTime == 0) { michael@0: return false; michael@0: } michael@0: michael@0: if (docLastModifiedTime > mOfflineCacheLastModifiedTime) { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::OpenCacheInputStream(nsICacheEntry* cacheEntry, bool startBuffering) michael@0: { michael@0: nsresult rv; michael@0: michael@0: bool usingSSL = false; michael@0: rv = mURI->SchemeIs("https", &usingSSL); michael@0: NS_ENSURE_SUCCESS(rv,rv); michael@0: michael@0: if (usingSSL) { michael@0: rv = cacheEntry->GetSecurityInfo( michael@0: getter_AddRefs(mCachedSecurityInfo)); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("failed to parse security-info [channel=%p, entry=%p]", michael@0: this, cacheEntry)); michael@0: NS_WARNING("failed to parse security-info"); michael@0: return rv; michael@0: } michael@0: michael@0: // XXX: We should not be skilling this check in the offline cache michael@0: // case, but we have to do so now to work around bug 794507. michael@0: MOZ_ASSERT(mCachedSecurityInfo || mLoadedFromApplicationCache); michael@0: if (!mCachedSecurityInfo && !mLoadedFromApplicationCache) { michael@0: LOG(("mCacheEntry->GetSecurityInfo returned success but did not " michael@0: "return the security info [channel=%p, entry=%p]", michael@0: this, cacheEntry)); michael@0: return NS_ERROR_UNEXPECTED; // XXX error code michael@0: } michael@0: } michael@0: michael@0: // Keep the conditions below in sync with the conditions in ReadFromCache. michael@0: michael@0: rv = NS_OK; michael@0: michael@0: if (WillRedirect(mCachedResponseHead)) { michael@0: // Do not even try to read the entity for a redirect because we do not michael@0: // return an entity to the application when we process redirects. michael@0: LOG(("Will skip read of cached redirect entity\n")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if ((mLoadFlags & nsICachingChannel::LOAD_ONLY_IF_MODIFIED) && michael@0: !mCachedContentIsPartial) { michael@0: // For LOAD_ONLY_IF_MODIFIED, we usually don't have to deal with the michael@0: // cached entity. michael@0: if (!mApplicationCacheForWrite) { michael@0: LOG(("Will skip read from cache based on LOAD_ONLY_IF_MODIFIED " michael@0: "load flag\n")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If offline caching has been requested and the offline cache needs michael@0: // updating, we must complete the call even if the main cache entry michael@0: // is up to date. We don't know yet for sure whether the offline michael@0: // cache needs updating because at this point we haven't opened it michael@0: // for writing yet, so we have to start reading the cached entity now michael@0: // just in case. michael@0: LOG(("May skip read from cache based on LOAD_ONLY_IF_MODIFIED " michael@0: "load flag\n")); michael@0: } michael@0: michael@0: // Open an input stream for the entity, so that the call to OpenInputStream michael@0: // happens off the main thread. michael@0: nsCOMPtr stream; michael@0: rv = cacheEntry->OpenInputStream(0, getter_AddRefs(stream)); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("Failed to open cache input stream [channel=%p, " michael@0: "mCacheEntry=%p]", this, cacheEntry)); michael@0: return rv; michael@0: } michael@0: michael@0: if (startBuffering) { michael@0: bool nonBlocking; michael@0: rv = stream->IsNonBlocking(&nonBlocking); michael@0: if (NS_SUCCEEDED(rv) && nonBlocking) michael@0: startBuffering = false; michael@0: } michael@0: michael@0: if (!startBuffering) { michael@0: // Bypass wrapping the input stream for the new cache back-end since michael@0: // nsIStreamTransportService expects a blocking stream. Preloading of michael@0: // the data must be done on the level of the cache backend, internally. michael@0: // michael@0: // We do not connect the stream to the stream transport service if we michael@0: // have to validate the entry with the server. If we did, we would get michael@0: // into a race condition between the stream transport service reading michael@0: // the existing contents and the opening of the cache entry's output michael@0: // stream to write the new contents in the case where we get a non-304 michael@0: // response. michael@0: LOG(("Opened cache input stream without buffering [channel=%p, " michael@0: "mCacheEntry=%p, stream=%p]", this, michael@0: cacheEntry, stream.get())); michael@0: mCacheInputStream.takeOver(stream); michael@0: return rv; michael@0: } michael@0: michael@0: // Have the stream transport service start reading the entity on one of its michael@0: // background threads. michael@0: michael@0: nsCOMPtr transport; michael@0: nsCOMPtr wrapper; michael@0: michael@0: nsCOMPtr sts = michael@0: do_GetService(kStreamTransportServiceCID, &rv); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = sts->CreateInputTransport(stream, int64_t(-1), int64_t(-1), michael@0: true, getter_AddRefs(transport)); michael@0: } michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = transport->OpenInputStream(0, 0, 0, getter_AddRefs(wrapper)); michael@0: } michael@0: if (NS_SUCCEEDED(rv)) { michael@0: LOG(("Opened cache input stream [channel=%p, wrapper=%p, " michael@0: "transport=%p, stream=%p]", this, wrapper.get(), michael@0: transport.get(), stream.get())); michael@0: } else { michael@0: LOG(("Failed to open cache input stream [channel=%p, " michael@0: "wrapper=%p, transport=%p, stream=%p]", this, michael@0: wrapper.get(), transport.get(), stream.get())); michael@0: michael@0: stream->Close(); michael@0: return rv; michael@0: } michael@0: michael@0: mCacheInputStream.takeOver(wrapper); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Actually process the cached response that we started to handle in CheckCache michael@0: // and/or StartBufferingCachedEntity. michael@0: nsresult michael@0: nsHttpChannel::ReadFromCache(bool alreadyMarkedValid) michael@0: { michael@0: NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE); michael@0: NS_ENSURE_TRUE(mCachedContentIsValid, NS_ERROR_FAILURE); michael@0: michael@0: LOG(("nsHttpChannel::ReadFromCache [this=%p] " michael@0: "Using cached copy of: %s\n", this, mSpec.get())); michael@0: michael@0: if (mCachedResponseHead) michael@0: mResponseHead = mCachedResponseHead; michael@0: michael@0: UpdateInhibitPersistentCachingFlag(); michael@0: michael@0: // if we don't already have security info, try to get it from the cache michael@0: // entry. there are two cases to consider here: 1) we are just reading michael@0: // from the cache, or 2) this may be due to a 304 not modified response, michael@0: // in which case we could have security info from a socket transport. michael@0: if (!mSecurityInfo) michael@0: mSecurityInfo = mCachedSecurityInfo; michael@0: michael@0: if (!alreadyMarkedValid && !mCachedContentIsPartial) { michael@0: // We validated the entry, and we have write access to the cache, so michael@0: // mark the cache entry as valid in order to allow others access to michael@0: // this cache entry. michael@0: // michael@0: // TODO: This should be done asynchronously so we don't take the cache michael@0: // service lock on the main thread. michael@0: mCacheEntry->MaybeMarkValid(); michael@0: } michael@0: michael@0: nsresult rv; michael@0: michael@0: // Keep the conditions below in sync with the conditions in michael@0: // StartBufferingCachedEntity. michael@0: michael@0: if (WillRedirect(mResponseHead)) { michael@0: // TODO: Bug 759040 - We should call HandleAsyncRedirect directly here, michael@0: // to avoid event dispatching latency. michael@0: MOZ_ASSERT(!mCacheInputStream); michael@0: LOG(("Skipping skip read of cached redirect entity\n")); michael@0: return AsyncCall(&nsHttpChannel::HandleAsyncRedirect); michael@0: } michael@0: michael@0: if ((mLoadFlags & LOAD_ONLY_IF_MODIFIED) && !mCachedContentIsPartial) { michael@0: if (!mApplicationCacheForWrite) { michael@0: LOG(("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED " michael@0: "load flag\n")); michael@0: MOZ_ASSERT(!mCacheInputStream); michael@0: // TODO: Bug 759040 - We should call HandleAsyncNotModified directly michael@0: // here, to avoid event dispatching latency. michael@0: return AsyncCall(&nsHttpChannel::HandleAsyncNotModified); michael@0: } michael@0: michael@0: if (!ShouldUpdateOfflineCacheEntry()) { michael@0: LOG(("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED " michael@0: "load flag (mApplicationCacheForWrite not null case)\n")); michael@0: mCacheInputStream.CloseAndRelease(); michael@0: // TODO: Bug 759040 - We should call HandleAsyncNotModified directly michael@0: // here, to avoid event dispatching latency. michael@0: return AsyncCall(&nsHttpChannel::HandleAsyncNotModified); michael@0: } michael@0: } michael@0: michael@0: MOZ_ASSERT(mCacheInputStream); michael@0: if (!mCacheInputStream) { michael@0: NS_ERROR("mCacheInputStream is null but we're expecting to " michael@0: "be able to read from it."); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: michael@0: nsCOMPtr inputStream = mCacheInputStream.forget(); michael@0: michael@0: rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump), inputStream, michael@0: int64_t(-1), int64_t(-1), 0, 0, true); michael@0: if (NS_FAILED(rv)) { michael@0: inputStream->Close(); michael@0: return rv; michael@0: } michael@0: michael@0: rv = mCachePump->AsyncRead(this, mListenerContext); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (mTimingEnabled) michael@0: mCacheReadStart = TimeStamp::Now(); michael@0: michael@0: uint32_t suspendCount = mSuspendCount; michael@0: while (suspendCount--) michael@0: mCachePump->Suspend(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::CloseCacheEntry(bool doomOnFailure) michael@0: { michael@0: mCacheInputStream.CloseAndRelease(); michael@0: michael@0: if (!mCacheEntry) michael@0: return; michael@0: michael@0: LOG(("nsHttpChannel::CloseCacheEntry [this=%p] mStatus=%x mCacheEntryIsWriteOnly=%x", michael@0: this, mStatus, mCacheEntryIsWriteOnly)); michael@0: michael@0: // If we have begun to create or replace a cache entry, and that cache michael@0: // entry is not complete and not resumable, then it needs to be doomed. michael@0: // Otherwise, CheckCache will make the mistake of thinking that the michael@0: // partial cache entry is complete. michael@0: michael@0: bool doom = false; michael@0: if (mInitedCacheEntry) { michael@0: MOZ_ASSERT(mResponseHead, "oops"); michael@0: if (NS_FAILED(mStatus) && doomOnFailure && michael@0: mCacheEntryIsWriteOnly && !mResponseHead->IsResumable()) michael@0: doom = true; michael@0: } michael@0: else if (mCacheEntryIsWriteOnly) michael@0: doom = true; michael@0: michael@0: if (doom) { michael@0: LOG((" dooming cache entry!!")); michael@0: mCacheEntry->AsyncDoom(nullptr); michael@0: } michael@0: michael@0: mCachedResponseHead = nullptr; michael@0: michael@0: mCachePump = nullptr; michael@0: mCacheEntry = nullptr; michael@0: mCacheEntryIsWriteOnly = false; michael@0: mInitedCacheEntry = false; michael@0: } michael@0: michael@0: michael@0: void michael@0: nsHttpChannel::CloseOfflineCacheEntry() michael@0: { michael@0: if (!mOfflineCacheEntry) michael@0: return; michael@0: michael@0: LOG(("nsHttpChannel::CloseOfflineCacheEntry [this=%p]", this)); michael@0: michael@0: if (NS_FAILED(mStatus)) { michael@0: mOfflineCacheEntry->AsyncDoom(nullptr); michael@0: } michael@0: else { michael@0: bool succeeded; michael@0: if (NS_SUCCEEDED(GetRequestSucceeded(&succeeded)) && !succeeded) michael@0: mOfflineCacheEntry->AsyncDoom(nullptr); michael@0: } michael@0: michael@0: mOfflineCacheEntry = nullptr; michael@0: } michael@0: michael@0: michael@0: // Initialize the cache entry for writing. michael@0: // - finalize storage policy michael@0: // - store security info michael@0: // - update expiration time michael@0: // - store headers and other meta data michael@0: nsresult michael@0: nsHttpChannel::InitCacheEntry() michael@0: { michael@0: nsresult rv; michael@0: michael@0: NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_UNEXPECTED); michael@0: // if only reading, nothing to be done here. michael@0: if (mCacheEntryIsReadOnly) michael@0: return NS_OK; michael@0: michael@0: // Don't cache the response again if already cached... michael@0: if (mCachedContentIsValid) michael@0: return NS_OK; michael@0: michael@0: LOG(("nsHttpChannel::InitCacheEntry [this=%p entry=%p]\n", michael@0: this, mCacheEntry.get())); michael@0: michael@0: bool recreate = !mCacheEntryIsWriteOnly; michael@0: bool dontPersist = mLoadFlags & INHIBIT_PERSISTENT_CACHING; michael@0: michael@0: if (!recreate && dontPersist) { michael@0: // If the current entry is persistent but we inhibit peristence michael@0: // then force recreation of the entry as memory/only. michael@0: rv = mCacheEntry->GetPersistent(&recreate); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: michael@0: if (recreate) { michael@0: LOG((" we have a ready entry, but reading it again from the server -> recreating cache entry\n")); michael@0: nsCOMPtr currentEntry; michael@0: currentEntry.swap(mCacheEntry); michael@0: rv = currentEntry->Recreate(dontPersist, getter_AddRefs(mCacheEntry)); michael@0: if (NS_FAILED(rv)) { michael@0: LOG((" recreation failed, the response will not be cached")); michael@0: return NS_OK; michael@0: } michael@0: michael@0: mCacheEntryIsWriteOnly = true; michael@0: } michael@0: michael@0: // Set the expiration time for this cache entry michael@0: rv = UpdateExpirationTime(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = AddCacheEntryHeaders(mCacheEntry); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mInitedCacheEntry = true; michael@0: michael@0: // Don't perform the check when writing (doesn't make sense) michael@0: mConcurentCacheAccess = 0; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::UpdateInhibitPersistentCachingFlag() michael@0: { michael@0: // The no-store directive within the 'Cache-Control:' header indicates michael@0: // that we must not store the response in a persistent cache. michael@0: if (mResponseHead->NoStore()) michael@0: mLoadFlags |= INHIBIT_PERSISTENT_CACHING; michael@0: michael@0: // Only cache SSL content on disk if the pref is set michael@0: if (!gHttpHandler->IsPersistentHttpsCachingEnabled() && michael@0: mConnectionInfo->UsingSSL()) michael@0: mLoadFlags |= INHIBIT_PERSISTENT_CACHING; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::InitOfflineCacheEntry() michael@0: { michael@0: // This function can be called even when we fail to connect (bug 551990) michael@0: michael@0: if (!mOfflineCacheEntry) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!mResponseHead || mResponseHead->NoStore()) { michael@0: if (mResponseHead && mResponseHead->NoStore()) { michael@0: mOfflineCacheEntry->AsyncDoom(nullptr); michael@0: } michael@0: michael@0: CloseOfflineCacheEntry(); michael@0: michael@0: if (mResponseHead && mResponseHead->NoStore()) { michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // This entry's expiration time should match the main entry's expiration michael@0: // time. UpdateExpirationTime() will keep it in sync once the offline michael@0: // cache entry has been created. michael@0: if (mCacheEntry) { michael@0: uint32_t expirationTime; michael@0: nsresult rv = mCacheEntry->GetExpirationTime(&expirationTime); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mOfflineCacheEntry->SetExpirationTime(expirationTime); michael@0: } michael@0: michael@0: return AddCacheEntryHeaders(mOfflineCacheEntry); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsHttpChannel::AddCacheEntryHeaders(nsICacheEntry *entry) michael@0: { michael@0: nsresult rv; michael@0: michael@0: LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] begin", this)); michael@0: // Store secure data in memory only michael@0: if (mSecurityInfo) michael@0: entry->SetSecurityInfo(mSecurityInfo); michael@0: michael@0: // Store the HTTP request method with the cache entry so we can distinguish michael@0: // for example GET and HEAD responses. michael@0: rv = entry->SetMetaDataElement("request-method", michael@0: mRequestHead.Method().get()); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Store the HTTP authorization scheme used if any... michael@0: rv = StoreAuthorizationMetaData(entry); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Iterate over the headers listed in the Vary response header, and michael@0: // store the value of the corresponding request header so we can verify michael@0: // that it has not varied when we try to re-use the cached response at michael@0: // a later time. Take care to store "Cookie" headers only as hashes michael@0: // due to security considerations and the fact that they can be pretty michael@0: // large (bug 468426). We take care of "Vary: cookie" in ResponseWouldVary. michael@0: // michael@0: // NOTE: if "Vary: accept, cookie", then we will store the "accept" header michael@0: // in the cache. we could try to avoid needlessly storing the "accept" michael@0: // header in this case, but it doesn't seem worth the extra code to perform michael@0: // the check. michael@0: { michael@0: nsAutoCString buf, metaKey; michael@0: mResponseHead->GetHeader(nsHttp::Vary, buf); michael@0: if (!buf.IsEmpty()) { michael@0: NS_NAMED_LITERAL_CSTRING(prefix, "request-"); michael@0: michael@0: char *val = buf.BeginWriting(); // going to munge buf michael@0: char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val); michael@0: while (token) { michael@0: LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \ michael@0: "processing %s", this, token)); michael@0: if (*token != '*') { michael@0: nsHttpAtom atom = nsHttp::ResolveAtom(token); michael@0: const char *val = mRequestHead.PeekHeader(atom); michael@0: nsAutoCString hash; michael@0: if (val) { michael@0: // If cookie-header, store a hash of the value michael@0: if (atom == nsHttp::Cookie) { michael@0: LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \ michael@0: "cookie-value %s", this, val)); michael@0: rv = Hash(val, hash); michael@0: // If hash failed, store a string not very likely michael@0: // to be the result of subsequent hashes michael@0: if (NS_FAILED(rv)) michael@0: val = ""; michael@0: else michael@0: val = hash.get(); michael@0: michael@0: LOG((" hashed to %s\n", val)); michael@0: } michael@0: michael@0: // build cache meta data key and set meta data element... michael@0: metaKey = prefix + nsDependentCString(token); michael@0: entry->SetMetaDataElement(metaKey.get(), val); michael@0: } else { michael@0: LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \ michael@0: "clearing metadata for %s", this, token)); michael@0: metaKey = prefix + nsDependentCString(token); michael@0: entry->SetMetaDataElement(metaKey.get(), nullptr); michael@0: } michael@0: } michael@0: token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val); michael@0: } michael@0: } michael@0: } michael@0: michael@0: michael@0: // Store the received HTTP head with the cache entry as an element of michael@0: // the meta data. michael@0: nsAutoCString head; michael@0: mResponseHead->Flatten(head, true); michael@0: rv = entry->SetMetaDataElement("response-head", head.get()); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // Indicate we have successfully finished setting metadata on the cache entry. michael@0: rv = entry->MetaDataReady(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: inline void michael@0: GetAuthType(const char *challenge, nsCString &authType) michael@0: { michael@0: const char *p; michael@0: michael@0: // get the challenge type michael@0: if ((p = strchr(challenge, ' ')) != nullptr) michael@0: authType.Assign(challenge, p - challenge); michael@0: else michael@0: authType.Assign(challenge); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::StoreAuthorizationMetaData(nsICacheEntry *entry) michael@0: { michael@0: // Not applicable to proxy authorization... michael@0: const char *val = mRequestHead.PeekHeader(nsHttp::Authorization); michael@0: if (!val) michael@0: return NS_OK; michael@0: michael@0: // eg. [Basic realm="wally world"] michael@0: nsAutoCString buf; michael@0: GetAuthType(val, buf); michael@0: return entry->SetMetaDataElement("auth", buf.get()); michael@0: } michael@0: michael@0: // Finalize the cache entry michael@0: // - may need to rewrite response headers if any headers changed michael@0: // - may need to recalculate the expiration time if any headers changed michael@0: // - called only for freshly written cache entries michael@0: nsresult michael@0: nsHttpChannel::FinalizeCacheEntry() michael@0: { michael@0: LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p]\n", this)); michael@0: michael@0: if (mResponseHead && mResponseHeadersModified) { michael@0: // Set the expiration time for this cache entry michael@0: nsresult rv = UpdateExpirationTime(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Open an output stream to the cache entry and insert a listener tee into michael@0: // the chain of response listeners. michael@0: nsresult michael@0: nsHttpChannel::InstallCacheListener(int64_t offset) michael@0: { michael@0: nsresult rv; michael@0: michael@0: LOG(("Preparing to write data into the cache [uri=%s]\n", mSpec.get())); michael@0: michael@0: MOZ_ASSERT(mCacheEntry); michael@0: MOZ_ASSERT(mCacheEntryIsWriteOnly || mCachedContentIsPartial); michael@0: MOZ_ASSERT(mListener); michael@0: michael@0: // If the content is compressible and the server has not compressed it, michael@0: // mark the cache entry for compression. michael@0: if ((mResponseHead->PeekHeader(nsHttp::Content_Encoding) == nullptr) && ( michael@0: mResponseHead->ContentType().EqualsLiteral(TEXT_HTML) || michael@0: mResponseHead->ContentType().EqualsLiteral(TEXT_PLAIN) || michael@0: mResponseHead->ContentType().EqualsLiteral(TEXT_CSS) || michael@0: mResponseHead->ContentType().EqualsLiteral(TEXT_JAVASCRIPT) || michael@0: mResponseHead->ContentType().EqualsLiteral(TEXT_ECMASCRIPT) || michael@0: mResponseHead->ContentType().EqualsLiteral(TEXT_XML) || michael@0: mResponseHead->ContentType().EqualsLiteral(APPLICATION_JAVASCRIPT) || michael@0: mResponseHead->ContentType().EqualsLiteral(APPLICATION_ECMASCRIPT) || michael@0: mResponseHead->ContentType().EqualsLiteral(APPLICATION_XJAVASCRIPT) || michael@0: mResponseHead->ContentType().EqualsLiteral(APPLICATION_XHTML_XML))) { michael@0: rv = mCacheEntry->SetMetaDataElement("uncompressed-len", "0"); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("unable to mark cache entry for compression")); michael@0: } michael@0: } michael@0: michael@0: LOG(("Trading cache input stream for output stream [channel=%p]", this)); michael@0: michael@0: // We must close the input stream first because cache entries do not michael@0: // correctly handle having an output stream and input streams open at michael@0: // the same time. michael@0: mCacheInputStream.CloseAndRelease(); michael@0: michael@0: nsCOMPtr out; michael@0: rv = mCacheEntry->OpenOutputStream(offset, getter_AddRefs(out)); michael@0: if (rv == NS_ERROR_NOT_AVAILABLE) { michael@0: LOG((" entry doomed, not writing it [channel=%p]", this)); michael@0: // Entry is already doomed. michael@0: // This may happen when expiration time is set to past and the entry michael@0: // has been removed by the background eviction logic. michael@0: return NS_OK; michael@0: } michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // XXX disk cache does not support overlapped i/o yet michael@0: #if 0 michael@0: // Mark entry valid inorder to allow simultaneous reading... michael@0: rv = mCacheEntry->MarkValid(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: #endif michael@0: michael@0: nsCOMPtr tee = michael@0: do_CreateInstance(kStreamListenerTeeCID, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr serv = michael@0: do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr cacheIOTarget; michael@0: serv->GetIoTarget(getter_AddRefs(cacheIOTarget)); michael@0: michael@0: if (!cacheIOTarget) { michael@0: LOG(("nsHttpChannel::InstallCacheListener sync tee %p rv=%x " michael@0: "cacheIOTarget=%p", tee.get(), rv, cacheIOTarget.get())); michael@0: rv = tee->Init(mListener, out, nullptr); michael@0: } else { michael@0: LOG(("nsHttpChannel::InstallCacheListener async tee %p", tee.get())); michael@0: rv = tee->InitAsync(mListener, cacheIOTarget, out, nullptr); michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) return rv; michael@0: mListener = tee; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::InstallOfflineCacheListener(int64_t offset) michael@0: { michael@0: nsresult rv; michael@0: michael@0: LOG(("Preparing to write data into the offline cache [uri=%s]\n", michael@0: mSpec.get())); michael@0: michael@0: MOZ_ASSERT(mOfflineCacheEntry); michael@0: MOZ_ASSERT(mListener); michael@0: michael@0: nsCOMPtr out; michael@0: rv = mOfflineCacheEntry->OpenOutputStream(offset, getter_AddRefs(out)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr tee = michael@0: do_CreateInstance(kStreamListenerTeeCID, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = tee->Init(mListener, out, nullptr); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mListener = tee; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::ClearBogusContentEncodingIfNeeded() michael@0: { michael@0: // For .gz files, apache sends both a Content-Type: application/x-gzip michael@0: // as well as Content-Encoding: gzip, which is completely wrong. In michael@0: // this case, we choose to ignore the rogue Content-Encoding header. We michael@0: // must do this early on so as to prevent it from being seen up stream. michael@0: // The same problem exists for Content-Encoding: compress in default michael@0: // Apache installs. michael@0: if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "gzip") && ( michael@0: mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP) || michael@0: mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP2) || michael@0: mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP3))) { michael@0: // clear the Content-Encoding header michael@0: mResponseHead->ClearHeader(nsHttp::Content_Encoding); michael@0: } michael@0: else if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "compress") && ( michael@0: mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS) || michael@0: mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS2))) { michael@0: // clear the Content-Encoding header michael@0: mResponseHead->ClearHeader(nsHttp::Content_Encoding); michael@0: } michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: nsHttpChannel::SetupReplacementChannel(nsIURI *newURI, michael@0: nsIChannel *newChannel, michael@0: bool preserveMethod) michael@0: { michael@0: LOG(("nsHttpChannel::SetupReplacementChannel " michael@0: "[this=%p newChannel=%p preserveMethod=%d]", michael@0: this, newChannel, preserveMethod)); michael@0: michael@0: nsresult rv = HttpBaseChannel::SetupReplacementChannel(newURI, newChannel, preserveMethod); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCOMPtr httpChannel = do_QueryInterface(newChannel); michael@0: if (!httpChannel) michael@0: return NS_OK; // no other options to set michael@0: michael@0: // convey the mApplyConversion flag (bug 91862) michael@0: nsCOMPtr encodedChannel = do_QueryInterface(httpChannel); michael@0: if (encodedChannel) michael@0: encodedChannel->SetApplyConversion(mApplyConversion); michael@0: michael@0: // transfer the resume information michael@0: if (mResuming) { michael@0: nsCOMPtr resumableChannel(do_QueryInterface(newChannel)); michael@0: if (!resumableChannel) { michael@0: NS_WARNING("Got asked to resume, but redirected to non-resumable channel!"); michael@0: return NS_ERROR_NOT_RESUMABLE; michael@0: } michael@0: resumableChannel->ResumeAt(mStartPos, mEntityID); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::AsyncProcessRedirection(uint32_t redirectType) michael@0: { michael@0: LOG(("nsHttpChannel::AsyncProcessRedirection [this=%p type=%u]\n", michael@0: this, redirectType)); michael@0: michael@0: // The channel is actually starting its operation now, at least because michael@0: // we want it to appear like being in the waiting phase until now. michael@0: MOZ_EVENT_TRACER_EXEC(this, "net::http::channel"); michael@0: MOZ_EVENT_TRACER_EXEC(this, "net::http::redirect-callbacks"); michael@0: michael@0: const char *location = mResponseHead->PeekHeader(nsHttp::Location); michael@0: michael@0: // if a location header was not given, then we can't perform the redirect, michael@0: // so just carry on as though this were a normal response. michael@0: if (!location) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // make sure non-ASCII characters in the location header are escaped. michael@0: nsAutoCString locationBuf; michael@0: if (NS_EscapeURL(location, -1, esc_OnlyNonASCII, locationBuf)) michael@0: location = locationBuf.get(); michael@0: michael@0: if (mRedirectionLimit == 0) { michael@0: LOG(("redirection limit reached!\n")); michael@0: return NS_ERROR_REDIRECT_LOOP; michael@0: } michael@0: michael@0: mRedirectType = redirectType; michael@0: michael@0: LOG(("redirecting to: %s [redirection-limit=%u]\n", michael@0: location, uint32_t(mRedirectionLimit))); michael@0: michael@0: nsresult rv = CreateNewURI(location, getter_AddRefs(mRedirectURI)); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("Invalid URI for redirect: Location: %s\n", location)); michael@0: return NS_ERROR_CORRUPTED_CONTENT; michael@0: } michael@0: michael@0: if (mApplicationCache) { michael@0: // if we are redirected to a different origin check if there is a fallback michael@0: // cache entry to fall back to. we don't care about file strict michael@0: // checking, at least mURI is not a file URI. michael@0: if (!NS_SecurityCompareURIs(mURI, mRedirectURI, false)) { michael@0: PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirectionAfterFallback); michael@0: bool waitingForRedirectCallback; michael@0: (void)ProcessFallback(&waitingForRedirectCallback); michael@0: if (waitingForRedirectCallback) michael@0: return NS_OK; michael@0: PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirectionAfterFallback); michael@0: } michael@0: } michael@0: michael@0: return ContinueProcessRedirectionAfterFallback(NS_OK); michael@0: } michael@0: michael@0: // Creates an URI to the given location using current URI for base and charset michael@0: nsresult michael@0: nsHttpChannel::CreateNewURI(const char *loc, nsIURI **newURI) michael@0: { michael@0: nsCOMPtr ioService; michael@0: nsresult rv = gHttpHandler->GetIOService(getter_AddRefs(ioService)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // the new uri should inherit the origin charset of the current uri michael@0: nsAutoCString originCharset; michael@0: rv = mURI->GetOriginCharset(originCharset); michael@0: if (NS_FAILED(rv)) michael@0: originCharset.Truncate(); michael@0: michael@0: return ioService->NewURI(nsDependentCString(loc), michael@0: originCharset.get(), michael@0: mURI, michael@0: newURI); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ContinueProcessRedirectionAfterFallback(nsresult rv) michael@0: { michael@0: if (NS_SUCCEEDED(rv) && mFallingBack) { michael@0: // do not continue with redirect processing, fallback is in michael@0: // progress now. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Kill the current cache entry if we are redirecting michael@0: // back to ourself. michael@0: bool redirectingBackToSameURI = false; michael@0: if (mCacheEntry && mCacheEntryIsWriteOnly && michael@0: NS_SUCCEEDED(mURI->Equals(mRedirectURI, &redirectingBackToSameURI)) && michael@0: redirectingBackToSameURI) michael@0: mCacheEntry->AsyncDoom(nullptr); michael@0: michael@0: // move the reference of the old location to the new one if the new michael@0: // one has none. michael@0: nsAutoCString ref; michael@0: rv = mRedirectURI->GetRef(ref); michael@0: if (NS_SUCCEEDED(rv) && ref.IsEmpty()) { michael@0: mURI->GetRef(ref); michael@0: if (!ref.IsEmpty()) { michael@0: // NOTE: SetRef will fail if mRedirectURI is immutable michael@0: // (e.g. an about: URI)... Oh well. michael@0: mRedirectURI->SetRef(ref); michael@0: } michael@0: } michael@0: michael@0: bool rewriteToGET = ShouldRewriteRedirectToGET(mRedirectType, michael@0: mRequestHead.ParsedMethod()); michael@0: michael@0: // prompt if the method is not safe (such as POST, PUT, DELETE, ...) michael@0: if (!rewriteToGET && !mRequestHead.IsSafeMethod()) { michael@0: rv = PromptTempRedirect(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: michael@0: nsCOMPtr ioService; michael@0: rv = gHttpHandler->GetIOService(getter_AddRefs(ioService)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr newChannel; michael@0: rv = ioService->NewChannelFromURI(mRedirectURI, getter_AddRefs(newChannel)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = SetupReplacementChannel(mRedirectURI, newChannel, !rewriteToGET); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: uint32_t redirectFlags; michael@0: if (nsHttp::IsPermanentRedirect(mRedirectType)) michael@0: redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT; michael@0: else michael@0: redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY; michael@0: michael@0: // verify that this is a legal redirect michael@0: mRedirectChannel = newChannel; michael@0: michael@0: PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection); michael@0: rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags); michael@0: michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = WaitForRedirectCallback(); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: AutoRedirectVetoNotifier notifier(this); michael@0: PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ContinueProcessRedirection(nsresult rv) michael@0: { michael@0: AutoRedirectVetoNotifier notifier(this); michael@0: michael@0: LOG(("ContinueProcessRedirection [rv=%x]\n", rv)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: NS_PRECONDITION(mRedirectChannel, "No redirect channel?"); michael@0: michael@0: // Make sure to do this _after_ calling OnChannelRedirect michael@0: mRedirectChannel->SetOriginalURI(mOriginalURI); michael@0: michael@0: // And now, the deprecated way michael@0: nsCOMPtr httpEventSink; michael@0: GetCallback(httpEventSink); michael@0: if (httpEventSink) { michael@0: // NOTE: nsIHttpEventSink is only used for compatibility with pre-1.8 michael@0: // versions. michael@0: rv = httpEventSink->OnRedirect(this, mRedirectChannel); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: // XXX we used to talk directly with the script security manager, but that michael@0: // should really be handled by the event sink implementation. michael@0: michael@0: // begin loading the new channel michael@0: rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // close down this channel michael@0: Cancel(NS_BINDING_REDIRECTED); michael@0: michael@0: notifier.RedirectSucceeded(); michael@0: michael@0: ReleaseListeners(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP nsHttpChannel::OnAuthAvailable() michael@0: { michael@0: LOG(("nsHttpChannel::OnAuthAvailable [this=%p]", this)); michael@0: michael@0: // setting mAuthRetryPending flag and resuming the transaction michael@0: // triggers process of throwing away the unauthenticated data already michael@0: // coming from the network michael@0: mAuthRetryPending = true; michael@0: mProxyAuthPending = false; michael@0: LOG(("Resuming the transaction, we got credentials from user")); michael@0: mTransactionPump->Resume(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsHttpChannel::OnAuthCancelled(bool userCancel) michael@0: { michael@0: LOG(("nsHttpChannel::OnAuthCancelled [this=%p]", this)); michael@0: michael@0: if (mTransactionPump) { michael@0: // If the channel is trying to authenticate to a proxy and michael@0: // that was canceled we cannot show the http response body michael@0: // from the 40x as that might mislead the user into thinking michael@0: // it was a end host response instead of a proxy reponse. michael@0: // This must check explicitly whether a proxy auth was being done michael@0: // because we do want to show the content if this is an error from michael@0: // the origin server. michael@0: if (mProxyAuthPending) michael@0: Cancel(NS_ERROR_PROXY_CONNECTION_REFUSED); michael@0: michael@0: // ensure call of OnStartRequest of the current listener here, michael@0: // it would not be called otherwise at all michael@0: nsresult rv = CallOnStartRequest(); michael@0: michael@0: // drop mAuthRetryPending flag and resume the transaction michael@0: // this resumes load of the unauthenticated content data (which michael@0: // may have been canceled if we don't want to show it) michael@0: mAuthRetryPending = false; michael@0: LOG(("Resuming the transaction, user cancelled the auth dialog")); michael@0: mTransactionPump->Resume(); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: mTransactionPump->Cancel(rv); michael@0: } michael@0: michael@0: mProxyAuthPending = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsISupports michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(nsHttpChannel, HttpBaseChannel) michael@0: NS_IMPL_RELEASE_INHERITED(nsHttpChannel, HttpBaseChannel) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsHttpChannel) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRequest) michael@0: NS_INTERFACE_MAP_ENTRY(nsIChannel) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsIStreamListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsIHttpChannel) michael@0: NS_INTERFACE_MAP_ENTRY(nsICacheInfoChannel) michael@0: NS_INTERFACE_MAP_ENTRY(nsICachingChannel) michael@0: NS_INTERFACE_MAP_ENTRY(nsIUploadChannel) michael@0: NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2) michael@0: NS_INTERFACE_MAP_ENTRY(nsICacheEntryOpenCallback) michael@0: NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal) michael@0: NS_INTERFACE_MAP_ENTRY(nsIResumableChannel) michael@0: NS_INTERFACE_MAP_ENTRY(nsITransportEventSink) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) michael@0: NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback) michael@0: NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel) michael@0: NS_INTERFACE_MAP_ENTRY(nsIHttpAuthenticableChannel) michael@0: NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer) michael@0: NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel) michael@0: NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback) michael@0: NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest) michael@0: NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDNSListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsIRequest michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::Cancel(nsresult status) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: LOG(("nsHttpChannel::Cancel [this=%p status=%x]\n", this, status)); michael@0: if (mCanceled) { michael@0: LOG((" ignoring; already canceled\n")); michael@0: return NS_OK; michael@0: } michael@0: if (mWaitingForRedirectCallback) { michael@0: LOG(("channel canceled during wait for redirect callback")); michael@0: } michael@0: mCanceled = true; michael@0: mStatus = status; michael@0: if (mProxyRequest) michael@0: mProxyRequest->Cancel(status); michael@0: if (mTransaction) michael@0: gHttpHandler->CancelTransaction(mTransaction, status); michael@0: if (mTransactionPump) michael@0: mTransactionPump->Cancel(status); michael@0: mCacheInputStream.CloseAndRelease(); michael@0: if (mCachePump) michael@0: mCachePump->Cancel(status); michael@0: if (mAuthProvider) michael@0: mAuthProvider->Cancel(status); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::Suspend() michael@0: { michael@0: NS_ENSURE_TRUE(mIsPending, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: LOG(("nsHttpChannel::Suspend [this=%p]\n", this)); michael@0: michael@0: ++mSuspendCount; michael@0: michael@0: if (mTransactionPump) michael@0: return mTransactionPump->Suspend(); michael@0: if (mCachePump) michael@0: return mCachePump->Suspend(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::Resume() michael@0: { michael@0: NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED); michael@0: michael@0: LOG(("nsHttpChannel::Resume [this=%p]\n", this)); michael@0: michael@0: if (--mSuspendCount == 0 && mCallOnResume) { michael@0: nsresult rv = AsyncCall(mCallOnResume); michael@0: mCallOnResume = nullptr; michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: if (mTransactionPump) michael@0: return mTransactionPump->Resume(); michael@0: if (mCachePump) michael@0: return mCachePump->Resume(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsIChannel michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetSecurityInfo(nsISupports **securityInfo) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(securityInfo); michael@0: *securityInfo = mSecurityInfo; michael@0: NS_IF_ADDREF(*securityInfo); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context) michael@0: { michael@0: MOZ_EVENT_TRACER_WAIT(this, "net::http::channel"); michael@0: michael@0: LOG(("nsHttpChannel::AsyncOpen [this=%p]\n", this)); michael@0: michael@0: NS_ENSURE_ARG_POINTER(listener); michael@0: NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); michael@0: NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED); michael@0: michael@0: nsresult rv; michael@0: michael@0: rv = NS_CheckPortSafety(mURI); michael@0: if (NS_FAILED(rv)) { michael@0: ReleaseListeners(); michael@0: return rv; michael@0: } michael@0: michael@0: // Remember the cookie header that was set, if any michael@0: const char *cookieHeader = mRequestHead.PeekHeader(nsHttp::Cookie); michael@0: if (cookieHeader) { michael@0: mUserSetCookieHeader = cookieHeader; michael@0: } michael@0: michael@0: AddCookiesToRequest(); michael@0: michael@0: // notify "http-on-opening-request" observers, but not if this is a redirect michael@0: if (!(mLoadFlags & LOAD_REPLACE)) { michael@0: gHttpHandler->OnOpeningRequest(this); michael@0: } michael@0: michael@0: mIsPending = true; michael@0: mWasOpened = true; michael@0: michael@0: mListener = listener; michael@0: mListenerContext = context; michael@0: michael@0: // add ourselves to the load group. from this point forward, we'll report michael@0: // all failures asynchronously. michael@0: if (mLoadGroup) michael@0: mLoadGroup->AddRequest(this, nullptr); michael@0: michael@0: // record asyncopen time unconditionally and clear it if we michael@0: // don't want it after OnModifyRequest() weighs in. But waiting for michael@0: // that to complete would mean we don't include proxy resolution in the michael@0: // timing. michael@0: mAsyncOpenTime = TimeStamp::Now(); michael@0: michael@0: // the only time we would already know the proxy information at this michael@0: // point would be if we were proxying a non-http protocol like ftp michael@0: if (!mProxyInfo && NS_SUCCEEDED(ResolveProxy())) michael@0: return NS_OK; michael@0: michael@0: rv = BeginConnect(); michael@0: if (NS_FAILED(rv)) michael@0: ReleaseListeners(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::BeginConnect() michael@0: { michael@0: LOG(("nsHttpChannel::BeginConnect [this=%p]\n", this)); michael@0: nsresult rv; michael@0: michael@0: // Construct connection info object michael@0: nsAutoCString host; michael@0: int32_t port = -1; michael@0: nsAutoCString username; michael@0: bool usingSSL = false; michael@0: michael@0: rv = mURI->SchemeIs("https", &usingSSL); michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = mURI->GetAsciiHost(host); michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = mURI->GetPort(&port); michael@0: if (NS_SUCCEEDED(rv)) michael@0: mURI->GetUsername(username); michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = mURI->GetAsciiSpec(mSpec); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // Reject the URL if it doesn't specify a host michael@0: if (host.IsEmpty()) michael@0: return NS_ERROR_MALFORMED_URI; michael@0: LOG(("host=%s port=%d\n", host.get(), port)); michael@0: LOG(("uri=%s\n", mSpec.get())); michael@0: michael@0: nsCOMPtr proxyInfo; michael@0: if (mProxyInfo) michael@0: proxyInfo = do_QueryInterface(mProxyInfo); michael@0: michael@0: mConnectionInfo = new nsHttpConnectionInfo(host, port, username, proxyInfo, usingSSL); michael@0: michael@0: mAuthProvider = michael@0: do_CreateInstance("@mozilla.org/network/http-channel-auth-provider;1", michael@0: &rv); michael@0: if (NS_SUCCEEDED(rv)) michael@0: rv = mAuthProvider->Init(this); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // check to see if authorization headers should be included michael@0: mAuthProvider->AddAuthorizationHeaders(); michael@0: michael@0: // notify "http-on-modify-request" observers michael@0: CallOnModifyRequestObservers(); michael@0: michael@0: // Check to see if we should redirect this channel elsewhere by michael@0: // nsIHttpChannel.redirectTo API request michael@0: if (mAPIRedirectToURI) { michael@0: return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect); michael@0: } michael@0: michael@0: // If mTimingEnabled flag is not set after OnModifyRequest() then michael@0: // clear the already recorded AsyncOpen value for consistency. michael@0: if (!mTimingEnabled) michael@0: mAsyncOpenTime = TimeStamp(); michael@0: michael@0: // when proxying only use the pipeline bit if ProxyPipelining() allows it. michael@0: if (!mConnectionInfo->UsingConnect() && mConnectionInfo->UsingHttpProxy()) { michael@0: mCaps &= ~NS_HTTP_ALLOW_PIPELINING; michael@0: if (gHttpHandler->ProxyPipelining()) michael@0: mCaps |= NS_HTTP_ALLOW_PIPELINING; michael@0: } michael@0: michael@0: // if this somehow fails we can go on without it michael@0: gHttpHandler->AddConnectionHeader(&mRequestHead.Headers(), mCaps); michael@0: michael@0: if (mLoadFlags & VALIDATE_ALWAYS || BYPASS_LOCAL_CACHE(mLoadFlags)) michael@0: mCaps |= NS_HTTP_REFRESH_DNS; michael@0: michael@0: if (!mConnectionInfo->UsingHttpProxy()) { michael@0: // Start a DNS lookup very early in case the real open is queued the DNS can michael@0: // happen in parallel. Do not do so in the presence of an HTTP proxy as michael@0: // all lookups other than for the proxy itself are done by the proxy. michael@0: // michael@0: // We keep the DNS prefetch object around so that we can retrieve michael@0: // timing information from it. There is no guarantee that we actually michael@0: // use the DNS prefetch data for the real connection, but as we keep michael@0: // this data around for 3 minutes by default, this should almost always michael@0: // be correct, and even when it isn't, the timing still represents _a_ michael@0: // valid DNS lookup timing for the site, even if it is not _the_ michael@0: // timing we used. michael@0: LOG(("nsHttpChannel::BeginConnect [this=%p] prefetching%s\n", michael@0: this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : "")); michael@0: mDNSPrefetch = new nsDNSPrefetch(mURI, this, mTimingEnabled); michael@0: mDNSPrefetch->PrefetchHigh(mCaps & NS_HTTP_REFRESH_DNS); michael@0: } michael@0: michael@0: // Adjust mCaps according to our request headers: michael@0: // - If "Connection: close" is set as a request header, then do not bother michael@0: // trying to establish a keep-alive connection. michael@0: if (mRequestHead.HasHeaderValue(nsHttp::Connection, "close")) michael@0: mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING); michael@0: michael@0: if (gHttpHandler->CriticalRequestPrioritization()) { michael@0: if (mLoadAsBlocking) michael@0: mCaps |= NS_HTTP_LOAD_AS_BLOCKING; michael@0: if (mLoadUnblocked) michael@0: mCaps |= NS_HTTP_LOAD_UNBLOCKED; michael@0: } michael@0: michael@0: // Force-Reload should reset the persistent connection pool for this host michael@0: if (mLoadFlags & LOAD_FRESH_CONNECTION) { michael@0: // just the initial document resets the whole pool michael@0: if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) { michael@0: gHttpHandler->ConnMgr()->DoShiftReloadConnectionCleanup(mConnectionInfo); michael@0: } michael@0: // each sub resource gets a fresh connection michael@0: mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING); michael@0: } michael@0: michael@0: // We may have been cancelled already, either by on-modify-request michael@0: // listeners or by load group observers; in that case, we should michael@0: // not send the request to the server michael@0: if (mCanceled) michael@0: rv = mStatus; michael@0: else michael@0: rv = Connect(); michael@0: if (NS_FAILED(rv)) { michael@0: LOG(("Calling AsyncAbort [rv=%x mCanceled=%i]\n", rv, mCanceled)); michael@0: CloseCacheEntry(true); michael@0: AsyncAbort(rv); michael@0: } else if (mLoadFlags & LOAD_CLASSIFY_URI) { michael@0: nsRefPtr classifier = new nsChannelClassifier(); michael@0: rv = classifier->Start(this); michael@0: if (NS_FAILED(rv)) { michael@0: Cancel(rv); michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsIHttpChannelInternal michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::SetupFallbackChannel(const char *aFallbackKey) michael@0: { michael@0: ENSURE_CALLED_BEFORE_CONNECT(); michael@0: michael@0: LOG(("nsHttpChannel::SetupFallbackChannel [this=%p, key=%s]", michael@0: this, aFallbackKey)); michael@0: mFallbackChannel = true; michael@0: mFallbackKey = aFallbackKey; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsISupportsPriority michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::SetPriority(int32_t value) michael@0: { michael@0: int16_t newValue = clamped(value, INT16_MIN, INT16_MAX); michael@0: if (mPriority == newValue) michael@0: return NS_OK; michael@0: mPriority = newValue; michael@0: if (mTransaction) michael@0: gHttpHandler->RescheduleTransaction(mTransaction, mPriority); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsIProtocolProxyCallback michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::OnProxyAvailable(nsICancelable *request, nsIChannel *channel, michael@0: nsIProxyInfo *pi, nsresult status) michael@0: { michael@0: LOG(("nsHttpChannel::OnProxyAvailable [this=%p pi=%p status=%x mStatus=%x]\n", michael@0: this, pi, status, mStatus)); michael@0: mProxyRequest = nullptr; michael@0: michael@0: nsresult rv; michael@0: michael@0: // If status is a failure code, then it means that we failed to resolve michael@0: // proxy info. That is a non-fatal error assuming it wasn't because the michael@0: // request was canceled. We just failover to DIRECT when proxy resolution michael@0: // fails (failure can mean that the PAC URL could not be loaded). michael@0: michael@0: if (NS_SUCCEEDED(status)) michael@0: mProxyInfo = pi; michael@0: michael@0: if (!gHttpHandler->Active()) { michael@0: LOG(("nsHttpChannel::OnProxyAvailable [this=%p] " michael@0: "Handler no longer active.\n", this)); michael@0: rv = NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: else { michael@0: rv = BeginConnect(); michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: Cancel(rv); michael@0: // Calling OnStart/OnStop synchronously here would mean doing it before michael@0: // returning from AsyncOpen which is a contract violation. Do it async. michael@0: nsRefPtr > event = michael@0: NS_NewRunnableMethod(this, &nsHttpChannel::DoNotifyListener); michael@0: rv = NS_DispatchToCurrentThread(event); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed To Dispatch DoNotifyListener"); michael@0: } michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsIProxiedChannel michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetProxyInfo(nsIProxyInfo **result) michael@0: { michael@0: if (!mConnectionInfo) michael@0: *result = mProxyInfo; michael@0: else michael@0: *result = mConnectionInfo->ProxyInfo(); michael@0: NS_IF_ADDREF(*result); michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsITimedChannel michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetDomainLookupStart(TimeStamp* _retval) { michael@0: if (mDNSPrefetch && mDNSPrefetch->TimingsValid()) michael@0: *_retval = mDNSPrefetch->StartTimestamp(); michael@0: else if (mTransaction) michael@0: *_retval = mTransaction->Timings().domainLookupStart; michael@0: else michael@0: *_retval = mTransactionTimings.domainLookupStart; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetDomainLookupEnd(TimeStamp* _retval) { michael@0: if (mDNSPrefetch && mDNSPrefetch->TimingsValid()) michael@0: *_retval = mDNSPrefetch->EndTimestamp(); michael@0: else if (mTransaction) michael@0: *_retval = mTransaction->Timings().domainLookupEnd; michael@0: else michael@0: *_retval = mTransactionTimings.domainLookupEnd; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetConnectStart(TimeStamp* _retval) { michael@0: if (mTransaction) michael@0: *_retval = mTransaction->Timings().connectStart; michael@0: else michael@0: *_retval = mTransactionTimings.connectStart; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetConnectEnd(TimeStamp* _retval) { michael@0: if (mTransaction) michael@0: *_retval = mTransaction->Timings().connectEnd; michael@0: else michael@0: *_retval = mTransactionTimings.connectEnd; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetRequestStart(TimeStamp* _retval) { michael@0: if (mTransaction) michael@0: *_retval = mTransaction->Timings().requestStart; michael@0: else michael@0: *_retval = mTransactionTimings.requestStart; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetResponseStart(TimeStamp* _retval) { michael@0: if (mTransaction) michael@0: *_retval = mTransaction->Timings().responseStart; michael@0: else michael@0: *_retval = mTransactionTimings.responseStart; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetResponseEnd(TimeStamp* _retval) { michael@0: if (mTransaction) michael@0: *_retval = mTransaction->Timings().responseEnd; michael@0: else michael@0: *_retval = mTransactionTimings.responseEnd; michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsIHttpAuthenticableChannel michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetIsSSL(bool *aIsSSL) michael@0: { michael@0: *aIsSSL = mConnectionInfo->UsingSSL(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetProxyMethodIsConnect(bool *aProxyMethodIsConnect) michael@0: { michael@0: *aProxyMethodIsConnect = mConnectionInfo->UsingConnect(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetServerResponseHeader(nsACString &value) michael@0: { michael@0: if (!mResponseHead) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: return mResponseHead->GetHeader(nsHttp::Server, value); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetProxyChallenges(nsACString &value) michael@0: { michael@0: if (!mResponseHead) michael@0: return NS_ERROR_UNEXPECTED; michael@0: return mResponseHead->GetHeader(nsHttp::Proxy_Authenticate, value); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetWWWChallenges(nsACString &value) michael@0: { michael@0: if (!mResponseHead) michael@0: return NS_ERROR_UNEXPECTED; michael@0: return mResponseHead->GetHeader(nsHttp::WWW_Authenticate, value); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::SetProxyCredentials(const nsACString &value) michael@0: { michael@0: return mRequestHead.SetHeader(nsHttp::Proxy_Authorization, value); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::SetWWWCredentials(const nsACString &value) michael@0: { michael@0: return mRequestHead.SetHeader(nsHttp::Authorization, value); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // Methods that nsIHttpAuthenticableChannel dupes from other IDLs, which we michael@0: // get from HttpBaseChannel, must be explicitly forwarded, because C++ sucks. michael@0: // michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetLoadFlags(nsLoadFlags *aLoadFlags) michael@0: { michael@0: return HttpBaseChannel::GetLoadFlags(aLoadFlags); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetURI(nsIURI **aURI) michael@0: { michael@0: return HttpBaseChannel::GetURI(aURI); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks) michael@0: { michael@0: return HttpBaseChannel::GetNotificationCallbacks(aCallbacks); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetLoadGroup(nsILoadGroup **aLoadGroup) michael@0: { michael@0: return HttpBaseChannel::GetLoadGroup(aLoadGroup); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetRequestMethod(nsACString& aMethod) michael@0: { michael@0: return HttpBaseChannel::GetRequestMethod(aMethod); michael@0: } michael@0: michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsIRequestObserver michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt) michael@0: { michael@0: PROFILER_LABEL("nsHttpChannel", "OnStartRequest"); michael@0: if (!(mCanceled || NS_FAILED(mStatus))) { michael@0: // capture the request's status, so our consumers will know ASAP of any michael@0: // connection failures, etc - bug 93581 michael@0: request->GetStatus(&mStatus); michael@0: } michael@0: michael@0: LOG(("nsHttpChannel::OnStartRequest [this=%p request=%p status=%x]\n", michael@0: this, request, mStatus)); michael@0: michael@0: // Make sure things are what we expect them to be... michael@0: MOZ_ASSERT(request == mCachePump || request == mTransactionPump, michael@0: "Unexpected request"); michael@0: MOZ_ASSERT(!(mTransactionPump && mCachePump) || mCachedContentIsPartial, michael@0: "If we have both pumps, the cache content must be partial"); michael@0: michael@0: if (!mSecurityInfo && !mCachePump && mTransaction) { michael@0: // grab the security info from the connection object; the transaction michael@0: // is guaranteed to own a reference to the connection. michael@0: mSecurityInfo = mTransaction->SecurityInfo(); michael@0: } michael@0: michael@0: // don't enter this block if we're reading from the cache... michael@0: if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) { michael@0: // mTransactionPump doesn't hit OnInputStreamReady and call this until michael@0: // all of the response headers have been acquired, so we can take ownership michael@0: // of them from the transaction. michael@0: mResponseHead = mTransaction->TakeResponseHead(); michael@0: // the response head may be null if the transaction was cancelled. in michael@0: // which case we just need to call OnStartRequest/OnStopRequest. michael@0: if (mResponseHead) michael@0: return ProcessResponse(); michael@0: michael@0: NS_WARNING("No response head in OnStartRequest"); michael@0: } michael@0: michael@0: // cache file could be deleted on our behalf, reload from network here. michael@0: if (mCacheEntry && mCachePump && CACHE_FILE_GONE(mStatus)) { michael@0: LOG((" cache file gone, reloading from server")); michael@0: mCacheEntry->AsyncDoom(nullptr); michael@0: nsresult rv = StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL); michael@0: if (NS_SUCCEEDED(rv)) michael@0: return NS_OK; michael@0: } michael@0: michael@0: // avoid crashing if mListener happens to be null... michael@0: if (!mListener) { michael@0: NS_NOTREACHED("mListener is null"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // on proxy errors, try to failover michael@0: if (mConnectionInfo->ProxyInfo() && michael@0: (mStatus == NS_ERROR_PROXY_CONNECTION_REFUSED || michael@0: mStatus == NS_ERROR_UNKNOWN_PROXY_HOST || michael@0: mStatus == NS_ERROR_NET_TIMEOUT)) { michael@0: michael@0: PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1); michael@0: if (NS_SUCCEEDED(ProxyFailover())) michael@0: return NS_OK; michael@0: PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1); michael@0: } michael@0: michael@0: return ContinueOnStartRequest2(NS_OK); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ContinueOnStartRequest1(nsresult result) michael@0: { michael@0: // Success indicates we passed ProxyFailover, in that case we must not continue michael@0: // with this code chain. michael@0: if (NS_SUCCEEDED(result)) michael@0: return NS_OK; michael@0: michael@0: return ContinueOnStartRequest2(result); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ContinueOnStartRequest2(nsresult result) michael@0: { michael@0: // on other request errors, try to fall back michael@0: if (NS_FAILED(mStatus)) { michael@0: PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3); michael@0: bool waitingForRedirectCallback; michael@0: ProcessFallback(&waitingForRedirectCallback); michael@0: if (waitingForRedirectCallback) michael@0: return NS_OK; michael@0: PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3); michael@0: } michael@0: michael@0: return ContinueOnStartRequest3(NS_OK); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::ContinueOnStartRequest3(nsresult result) michael@0: { michael@0: if (mFallingBack) michael@0: return NS_OK; michael@0: michael@0: return CallOnStartRequest(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status) michael@0: { michael@0: PROFILER_LABEL("network", "nsHttpChannel::OnStopRequest"); michael@0: LOG(("nsHttpChannel::OnStopRequest [this=%p request=%p status=%x]\n", michael@0: this, request, status)); michael@0: michael@0: if (mTimingEnabled && request == mCachePump) { michael@0: mCacheReadEnd = TimeStamp::Now(); michael@0: } michael@0: michael@0: // allow content to be cached if it was loaded successfully (bug #482935) michael@0: bool contentComplete = NS_SUCCEEDED(status); michael@0: michael@0: // honor the cancelation status even if the underlying transaction completed. michael@0: if (mCanceled || NS_FAILED(mStatus)) michael@0: status = mStatus; michael@0: michael@0: if (mCachedContentIsPartial) { michael@0: if (NS_SUCCEEDED(status)) { michael@0: // mTransactionPump should be suspended michael@0: MOZ_ASSERT(request != mTransactionPump, michael@0: "byte-range transaction finished prematurely"); michael@0: michael@0: if (request == mCachePump) { michael@0: bool streamDone; michael@0: status = OnDoneReadingPartialCacheEntry(&streamDone); michael@0: if (NS_SUCCEEDED(status) && !streamDone) michael@0: return status; michael@0: // otherwise, fall through and fire OnStopRequest... michael@0: } michael@0: else if (request == mTransactionPump) { michael@0: MOZ_ASSERT(mConcurentCacheAccess); michael@0: } michael@0: else michael@0: NS_NOTREACHED("unexpected request"); michael@0: } michael@0: // Do not to leave the transaction in a suspended state in error cases. michael@0: if (NS_FAILED(status) && mTransaction) michael@0: gHttpHandler->CancelTransaction(mTransaction, status); michael@0: } michael@0: michael@0: if (mTransaction) { michael@0: // determine if we should call DoAuthRetry michael@0: bool authRetry = mAuthRetryPending && NS_SUCCEEDED(status); michael@0: michael@0: // michael@0: // grab references to connection in case we need to retry an michael@0: // authentication request over it or use it for an upgrade michael@0: // to another protocol. michael@0: // michael@0: // this code relies on the code in nsHttpTransaction::Close, which michael@0: // tests for NS_HTTP_STICKY_CONNECTION to determine whether or not to michael@0: // keep the connection around after the transaction is finished. michael@0: // michael@0: nsRefPtr conn; michael@0: if (authRetry && (mCaps & NS_HTTP_STICKY_CONNECTION)) { michael@0: conn = mTransaction->GetConnectionReference(); michael@0: // This is so far a workaround to fix leak when reusing unpersistent michael@0: // connection for authentication retry. See bug 459620 comment 4 michael@0: // for details. michael@0: if (conn && !conn->IsPersistent()) michael@0: conn = nullptr; michael@0: } michael@0: michael@0: nsRefPtr stickyConn; michael@0: if (mCaps & NS_HTTP_STICKY_CONNECTION) michael@0: stickyConn = mTransaction->GetConnectionReference(); michael@0: michael@0: // at this point, we're done with the transaction michael@0: mTransactionTimings = mTransaction->Timings(); michael@0: mTransaction = nullptr; michael@0: mTransactionPump = nullptr; michael@0: michael@0: // We no longer need the dns prefetch object michael@0: if (mDNSPrefetch && mDNSPrefetch->TimingsValid()) { michael@0: mTransactionTimings.domainLookupStart = michael@0: mDNSPrefetch->StartTimestamp(); michael@0: mTransactionTimings.domainLookupEnd = michael@0: mDNSPrefetch->EndTimestamp(); michael@0: } michael@0: mDNSPrefetch = nullptr; michael@0: michael@0: // handle auth retry... michael@0: if (authRetry) { michael@0: mAuthRetryPending = false; michael@0: status = DoAuthRetry(conn); michael@0: if (NS_SUCCEEDED(status)) michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If DoAuthRetry failed, or if we have been cancelled since showing michael@0: // the auth. dialog, then we need to send OnStartRequest now michael@0: if (authRetry || (mAuthRetryPending && NS_FAILED(status))) { michael@0: MOZ_ASSERT(NS_FAILED(status), "should have a failure code here"); michael@0: // NOTE: since we have a failure status, we can ignore the return michael@0: // value from onStartRequest. michael@0: if (mListener) { michael@0: mListener->OnStartRequest(this, mListenerContext); michael@0: } else { michael@0: NS_WARNING("OnStartRequest skipped because of null listener"); michael@0: } michael@0: } michael@0: michael@0: // if this transaction has been replaced, then bail. michael@0: if (mTransactionReplaced) michael@0: return NS_OK; michael@0: michael@0: if (mUpgradeProtocolCallback && stickyConn && michael@0: mResponseHead && mResponseHead->Status() == 101) { michael@0: gHttpHandler->ConnMgr()->CompleteUpgrade(stickyConn, michael@0: mUpgradeProtocolCallback); michael@0: } michael@0: } michael@0: michael@0: mIsPending = false; michael@0: michael@0: // if needed, check cache entry has all data we expect michael@0: if (mCacheEntry && mCachePump && michael@0: mConcurentCacheAccess && contentComplete) { michael@0: int64_t size, contentLength; michael@0: nsresult rv = CheckPartial(mCacheEntry, &size, &contentLength); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: if (size == int64_t(-1)) { michael@0: // mayhemer TODO - we have to restart read from cache here at the size offset michael@0: MOZ_ASSERT(false); michael@0: LOG((" cache entry write is still in progress, but we just " michael@0: "finished reading the cache entry")); michael@0: } michael@0: else if (contentLength != int64_t(-1) && contentLength != size) { michael@0: LOG((" concurrent cache entry write has been interrupted")); michael@0: mCachedResponseHead = mResponseHead; michael@0: rv = MaybeSetupByteRangeRequest(size, contentLength); michael@0: if (NS_SUCCEEDED(rv) && mIsPartialRequest) { michael@0: // Prevent read from cache again michael@0: mCachedContentIsValid = 0; michael@0: mCachedContentIsPartial = 1; michael@0: michael@0: // Perform the range request michael@0: rv = ContinueConnect(); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: LOG((" performing range request")); michael@0: mCachePump = nullptr; michael@0: return NS_OK; michael@0: } else { michael@0: LOG((" but range request perform failed 0x%08x", rv)); michael@0: status = NS_ERROR_NET_INTERRUPT; michael@0: } michael@0: } michael@0: else { michael@0: LOG((" but range request setup failed rv=0x%08x, failing load", rv)); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: mStatus = status; michael@0: michael@0: // perform any final cache operations before we close the cache entry. michael@0: if (mCacheEntry && mRequestTimeInitialized) { michael@0: bool writeAccess; michael@0: // New implementation just returns value of the !mCacheEntryIsReadOnly flag passed in. michael@0: // Old implementation checks on nsICache::ACCESS_WRITE flag. michael@0: mCacheEntry->HasWriteAccess(!mCacheEntryIsReadOnly, &writeAccess); michael@0: if (writeAccess) { michael@0: FinalizeCacheEntry(); michael@0: } michael@0: } michael@0: michael@0: // Register entry to the Performance resource timing michael@0: nsPerformance* documentPerformance = GetPerformance(); michael@0: if (documentPerformance) { michael@0: documentPerformance->AddEntry(this, this); michael@0: } michael@0: michael@0: if (mListener) { michael@0: LOG((" calling OnStopRequest\n")); michael@0: mListener->OnStopRequest(this, mListenerContext, status); michael@0: } michael@0: michael@0: MOZ_EVENT_TRACER_DONE(this, "net::http::channel"); michael@0: michael@0: CloseCacheEntry(!contentComplete); michael@0: michael@0: if (mOfflineCacheEntry) michael@0: CloseOfflineCacheEntry(); michael@0: michael@0: if (mLoadGroup) michael@0: mLoadGroup->RemoveRequest(this, nullptr, status); michael@0: michael@0: // We don't need this info anymore michael@0: CleanRedirectCacheChainIfNecessary(); michael@0: michael@0: ReleaseListeners(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsIStreamListener michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: class OnTransportStatusAsyncEvent : public nsRunnable michael@0: { michael@0: public: michael@0: OnTransportStatusAsyncEvent(nsITransportEventSink* aEventSink, michael@0: nsresult aTransportStatus, michael@0: uint64_t aProgress, michael@0: uint64_t aProgressMax) michael@0: : mEventSink(aEventSink) michael@0: , mTransportStatus(aTransportStatus) michael@0: , mProgress(aProgress) michael@0: , mProgressMax(aProgressMax) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread(), "Shouldn't be created on main thread"); michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Should run on main thread"); michael@0: if (mEventSink) { michael@0: mEventSink->OnTransportStatus(nullptr, mTransportStatus, michael@0: mProgress, mProgressMax); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsCOMPtr mEventSink; michael@0: nsresult mTransportStatus; michael@0: uint64_t mProgress; michael@0: uint64_t mProgressMax; michael@0: }; michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, michael@0: nsIInputStream *input, michael@0: uint64_t offset, uint32_t count) michael@0: { michael@0: PROFILER_LABEL("network", "nsHttpChannel::OnDataAvailable"); michael@0: LOG(("nsHttpChannel::OnDataAvailable [this=%p request=%p offset=%llu count=%u]\n", michael@0: this, request, offset, count)); michael@0: michael@0: // don't send out OnDataAvailable notifications if we've been canceled. michael@0: if (mCanceled) michael@0: return mStatus; michael@0: michael@0: MOZ_ASSERT(mResponseHead, "No response head in ODA!!"); michael@0: michael@0: MOZ_ASSERT(!(mCachedContentIsPartial && (request == mTransactionPump)), michael@0: "transaction pump not suspended"); michael@0: michael@0: if (mAuthRetryPending || (request == mTransactionPump && mTransactionReplaced)) { michael@0: uint32_t n; michael@0: return input->ReadSegments(NS_DiscardSegment, nullptr, count, &n); michael@0: } michael@0: michael@0: if (mListener) { michael@0: // michael@0: // synthesize transport progress event. we do this here since we want michael@0: // to delay OnProgress events until we start streaming data. this is michael@0: // crucially important since it impacts the lock icon (see bug 240053). michael@0: // michael@0: nsresult transportStatus; michael@0: if (request == mCachePump) michael@0: transportStatus = NS_NET_STATUS_READING; michael@0: else michael@0: transportStatus = NS_NET_STATUS_RECEIVING_FROM; michael@0: michael@0: // mResponseHead may reference new or cached headers, but either way it michael@0: // holds our best estimate of the total content length. Even in the case michael@0: // of a byte range request, the content length stored in the cached michael@0: // response headers is what we want to use here. michael@0: michael@0: uint64_t progressMax(uint64_t(mResponseHead->ContentLength())); michael@0: uint64_t progress = mLogicalOffset + uint64_t(count); michael@0: michael@0: if (progress > progressMax) michael@0: NS_WARNING("unexpected progress values - " michael@0: "is server exceeding content length?"); michael@0: michael@0: if (NS_IsMainThread()) { michael@0: OnTransportStatus(nullptr, transportStatus, progress, progressMax); michael@0: } else { michael@0: nsresult rv = NS_DispatchToMainThread( michael@0: new OnTransportStatusAsyncEvent(this, transportStatus, michael@0: progress, progressMax)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // michael@0: // we have to manually keep the logical offset of the stream up-to-date. michael@0: // we cannot depend solely on the offset provided, since we may have michael@0: // already streamed some data from another source (see, for example, michael@0: // OnDoneReadingPartialCacheEntry). michael@0: // michael@0: if (!mLogicalOffset) michael@0: MOZ_EVENT_TRACER_EXEC(this, "net::http::channel"); michael@0: michael@0: int64_t offsetBefore = 0; michael@0: nsCOMPtr seekable = do_QueryInterface(input); michael@0: if (seekable && NS_FAILED(seekable->Tell(&offsetBefore))) { michael@0: seekable = nullptr; michael@0: } michael@0: michael@0: nsresult rv = mListener->OnDataAvailable(this, michael@0: mListenerContext, michael@0: input, michael@0: mLogicalOffset, michael@0: count); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: // by contract mListener must read all of "count" bytes, but michael@0: // nsInputStreamPump is tolerant to seekable streams that violate that michael@0: // and it will redeliver incompletely read data. So we need to do michael@0: // the same thing when updating the progress counter to stay in sync. michael@0: int64_t offsetAfter, delta; michael@0: if (seekable && NS_SUCCEEDED(seekable->Tell(&offsetAfter))) { michael@0: delta = offsetAfter - offsetBefore; michael@0: if (delta != count) { michael@0: count = delta; michael@0: michael@0: NS_WARNING("Listener OnDataAvailable contract violation"); michael@0: nsCOMPtr consoleService = michael@0: do_GetService(NS_CONSOLESERVICE_CONTRACTID); michael@0: nsAutoString message michael@0: (NS_LITERAL_STRING( michael@0: "http channel Listener OnDataAvailable contract violation")); michael@0: if (consoleService) { michael@0: consoleService->LogStringMessage(message.get()); michael@0: } michael@0: } michael@0: } michael@0: mLogicalOffset += count; michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsIThreadRetargetableRequest michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::RetargetDeliveryTo(nsIEventTarget* aNewTarget) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only"); michael@0: michael@0: NS_ENSURE_ARG(aNewTarget); michael@0: if (aNewTarget == NS_GetCurrentThread()) { michael@0: NS_WARNING("Retargeting delivery to same thread"); michael@0: return NS_OK; michael@0: } michael@0: NS_ENSURE_TRUE(mTransactionPump || mCachePump, NS_ERROR_NOT_AVAILABLE); michael@0: michael@0: nsresult rv = NS_OK; michael@0: // If both cache pump and transaction pump exist, we're probably dealing michael@0: // with partially cached content. So, we must be able to retarget both. michael@0: nsCOMPtr retargetableCachePump; michael@0: nsCOMPtr retargetableTransactionPump; michael@0: if (mCachePump) { michael@0: retargetableCachePump = do_QueryObject(mCachePump); michael@0: // nsInputStreamPump should implement this interface. michael@0: MOZ_ASSERT(retargetableCachePump); michael@0: rv = retargetableCachePump->RetargetDeliveryTo(aNewTarget); michael@0: } michael@0: if (NS_SUCCEEDED(rv) && mTransactionPump) { michael@0: retargetableTransactionPump = do_QueryObject(mTransactionPump); michael@0: // nsInputStreamPump should implement this interface. michael@0: MOZ_ASSERT(retargetableTransactionPump); michael@0: rv = retargetableTransactionPump->RetargetDeliveryTo(aNewTarget); michael@0: michael@0: // If retarget fails for transaction pump, we must restore mCachePump. michael@0: if (NS_FAILED(rv) && retargetableCachePump) { michael@0: nsCOMPtr mainThread; michael@0: rv = NS_GetMainThread(getter_AddRefs(mainThread)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = retargetableCachePump->RetargetDeliveryTo(mainThread); michael@0: } michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsThreadRetargetableStreamListener michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::CheckListenerChain() michael@0: { michael@0: NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!"); michael@0: nsresult rv = NS_OK; michael@0: nsCOMPtr retargetableListener = michael@0: do_QueryInterface(mListener, &rv); michael@0: if (retargetableListener) { michael@0: rv = retargetableListener->CheckListenerChain(); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsITransportEventSink michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::OnTransportStatus(nsITransport *trans, nsresult status, michael@0: uint64_t progress, uint64_t progressMax) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread only"); michael@0: // cache the progress sink so we don't have to query for it each time. michael@0: if (!mProgressSink) michael@0: GetCallback(mProgressSink); michael@0: michael@0: if (status == NS_NET_STATUS_CONNECTED_TO || michael@0: status == NS_NET_STATUS_WAITING_FOR) { michael@0: nsCOMPtr socketTransport = michael@0: do_QueryInterface(trans); michael@0: if (socketTransport) { michael@0: socketTransport->GetSelfAddr(&mSelfAddr); michael@0: socketTransport->GetPeerAddr(&mPeerAddr); michael@0: } michael@0: } michael@0: michael@0: // block socket status event after Cancel or OnStopRequest has been called. michael@0: if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending && !(mLoadFlags & LOAD_BACKGROUND)) { michael@0: LOG(("sending status notification [this=%p status=%x progress=%llu/%llu]\n", michael@0: this, status, progress, progressMax)); michael@0: michael@0: nsAutoCString host; michael@0: mURI->GetHost(host); michael@0: mProgressSink->OnStatus(this, nullptr, status, michael@0: NS_ConvertUTF8toUTF16(host).get()); michael@0: michael@0: if (progress > 0) { michael@0: MOZ_ASSERT(progress <= progressMax, "unexpected progress values"); michael@0: // Try to get mProgressSink if it was nulled out during OnStatus. michael@0: if (!mProgressSink) { michael@0: GetCallback(mProgressSink); michael@0: } michael@0: if (mProgressSink) { michael@0: mProgressSink->OnProgress(this, nullptr, progress, progressMax); michael@0: } michael@0: } michael@0: } michael@0: #ifdef DEBUG michael@0: else michael@0: LOG(("skipping status notification [this=%p sink=%p pending=%u background=%x]\n", michael@0: this, mProgressSink.get(), mIsPending, (mLoadFlags & LOAD_BACKGROUND))); michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsICacheInfoChannel michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::IsFromCache(bool *value) michael@0: { michael@0: if (!mIsPending) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: // return false if reading a partial cache entry; the data isn't entirely michael@0: // from the cache! michael@0: michael@0: *value = (mCachePump || (mLoadFlags & LOAD_ONLY_IF_MODIFIED)) && michael@0: mCachedContentIsValid && !mCachedContentIsPartial; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetCacheTokenExpirationTime(uint32_t *_retval) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(_retval); michael@0: if (!mCacheEntry) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: return mCacheEntry->GetExpirationTime(_retval); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetCacheTokenCachedCharset(nsACString &_retval) michael@0: { michael@0: nsresult rv; michael@0: michael@0: if (!mCacheEntry) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: nsXPIDLCString cachedCharset; michael@0: rv = mCacheEntry->GetMetaDataElement("charset", michael@0: getter_Copies(cachedCharset)); michael@0: if (NS_SUCCEEDED(rv)) michael@0: _retval = cachedCharset; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::SetCacheTokenCachedCharset(const nsACString &aCharset) michael@0: { michael@0: if (!mCacheEntry) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: return mCacheEntry->SetMetaDataElement("charset", michael@0: PromiseFlatCString(aCharset).get()); michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetCacheDomain(nsACString &value) michael@0: { michael@0: value = mCacheDomain; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::SetCacheDomain(const nsACString &value) michael@0: { michael@0: mCacheDomain = value; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsICachingChannel michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetCacheToken(nsISupports **token) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(token); michael@0: if (!mCacheEntry) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: return CallQueryInterface(mCacheEntry, token); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::SetCacheToken(nsISupports *token) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetOfflineCacheToken(nsISupports **token) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(token); michael@0: if (!mOfflineCacheEntry) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: return CallQueryInterface(mOfflineCacheEntry, token); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::SetOfflineCacheToken(nsISupports *token) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: class nsHttpChannelCacheKey MOZ_FINAL : public nsISupportsPRUint32, michael@0: public nsISupportsCString michael@0: { michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_DECL_NSISUPPORTSPRIMITIVE michael@0: NS_FORWARD_NSISUPPORTSPRUINT32(mSupportsPRUint32->) michael@0: michael@0: // Both interfaces declares toString method with the same signature. michael@0: // Thus we have to delegate only to nsISupportsPRUint32 implementation. michael@0: NS_IMETHOD GetData(nsACString & aData) michael@0: { michael@0: return mSupportsCString->GetData(aData); michael@0: } michael@0: NS_IMETHOD SetData(const nsACString & aData) michael@0: { michael@0: return mSupportsCString->SetData(aData); michael@0: } michael@0: michael@0: public: michael@0: nsresult SetData(uint32_t aPostID, const nsACString& aKey); michael@0: michael@0: protected: michael@0: nsCOMPtr mSupportsPRUint32; michael@0: nsCOMPtr mSupportsCString; michael@0: }; michael@0: michael@0: NS_IMPL_ADDREF(nsHttpChannelCacheKey) michael@0: NS_IMPL_RELEASE(nsHttpChannelCacheKey) michael@0: NS_INTERFACE_TABLE_HEAD(nsHttpChannelCacheKey) michael@0: NS_INTERFACE_TABLE_BEGIN michael@0: NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsHttpChannelCacheKey, michael@0: nsISupports, nsISupportsPRUint32) michael@0: NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsHttpChannelCacheKey, michael@0: nsISupportsPrimitive, nsISupportsPRUint32) michael@0: NS_INTERFACE_TABLE_ENTRY(nsHttpChannelCacheKey, michael@0: nsISupportsPRUint32) michael@0: NS_INTERFACE_TABLE_ENTRY(nsHttpChannelCacheKey, michael@0: nsISupportsCString) michael@0: NS_INTERFACE_TABLE_END michael@0: NS_INTERFACE_TABLE_TAIL michael@0: michael@0: NS_IMETHODIMP nsHttpChannelCacheKey::GetType(uint16_t *aType) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aType); michael@0: michael@0: *aType = TYPE_PRUINT32; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsHttpChannelCacheKey::SetData(uint32_t aPostID, michael@0: const nsACString& aKey) michael@0: { michael@0: nsresult rv; michael@0: michael@0: mSupportsCString = michael@0: do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mSupportsCString->SetData(aKey); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mSupportsPRUint32 = michael@0: do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: mSupportsPRUint32->SetData(aPostID); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetCacheKey(nsISupports **key) michael@0: { michael@0: // mayhemer: TODO - do we need this API? michael@0: michael@0: nsresult rv; michael@0: NS_ENSURE_ARG_POINTER(key); michael@0: michael@0: LOG(("nsHttpChannel::GetCacheKey [this=%p]\n", this)); michael@0: michael@0: *key = nullptr; michael@0: michael@0: nsRefPtr container = michael@0: new nsHttpChannelCacheKey(); michael@0: michael@0: if (!container) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: nsAutoCString cacheKey; michael@0: rv = GenerateCacheKey(mPostID, cacheKey); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = container->SetData(mPostID, cacheKey); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: return CallQueryInterface(container.get(), key); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::SetCacheKey(nsISupports *key) michael@0: { michael@0: nsresult rv; michael@0: michael@0: LOG(("nsHttpChannel::SetCacheKey [this=%p key=%p]\n", this, key)); michael@0: michael@0: ENSURE_CALLED_BEFORE_CONNECT(); michael@0: michael@0: if (!key) michael@0: mPostID = 0; michael@0: else { michael@0: // extract the post id michael@0: nsCOMPtr container = do_QueryInterface(key, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = container->GetData(&mPostID); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsIResumableChannel michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::ResumeAt(uint64_t aStartPos, michael@0: const nsACString& aEntityID) michael@0: { michael@0: LOG(("nsHttpChannel::ResumeAt [this=%p startPos=%llu id='%s']\n", michael@0: this, aStartPos, PromiseFlatCString(aEntityID).get())); michael@0: mEntityID = aEntityID; michael@0: mStartPos = aStartPos; michael@0: mResuming = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::DoAuthRetry(nsAHttpConnection *conn) michael@0: { michael@0: LOG(("nsHttpChannel::DoAuthRetry [this=%p]\n", this)); michael@0: michael@0: MOZ_ASSERT(!mTransaction, "should not have a transaction"); michael@0: nsresult rv; michael@0: michael@0: // toggle mIsPending to allow nsIObserver implementations to modify michael@0: // the request headers (bug 95044). michael@0: mIsPending = false; michael@0: michael@0: // fetch cookies, and add them to the request header. michael@0: // the server response could have included cookies that must be sent with michael@0: // this authentication attempt (bug 84794). michael@0: // TODO: save cookies from auth response and send them here (bug 572151). michael@0: AddCookiesToRequest(); michael@0: michael@0: // notify "http-on-modify-request" observers michael@0: CallOnModifyRequestObservers(); michael@0: michael@0: mIsPending = true; michael@0: michael@0: // get rid of the old response headers michael@0: mResponseHead = nullptr; michael@0: michael@0: // rewind the upload stream michael@0: if (mUploadStream) { michael@0: nsCOMPtr seekable = do_QueryInterface(mUploadStream); michael@0: if (seekable) michael@0: seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); michael@0: } michael@0: michael@0: // set sticky connection flag and disable pipelining. michael@0: mCaps |= NS_HTTP_STICKY_CONNECTION; michael@0: mCaps &= ~NS_HTTP_ALLOW_PIPELINING; michael@0: michael@0: // and create a new one... michael@0: rv = SetupTransaction(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // transfer ownership of connection to transaction michael@0: if (conn) michael@0: mTransaction->SetConnection(conn); michael@0: michael@0: rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = mTransactionPump->AsyncRead(this, nullptr); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: uint32_t suspendCount = mSuspendCount; michael@0: while (suspendCount--) michael@0: mTransactionPump->Suspend(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsIApplicationCacheChannel michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetApplicationCache(nsIApplicationCache **out) michael@0: { michael@0: NS_IF_ADDREF(*out = mApplicationCache); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::SetApplicationCache(nsIApplicationCache *appCache) michael@0: { michael@0: ENSURE_CALLED_BEFORE_CONNECT(); michael@0: michael@0: mApplicationCache = appCache; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetApplicationCacheForWrite(nsIApplicationCache **out) michael@0: { michael@0: NS_IF_ADDREF(*out = mApplicationCacheForWrite); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::SetApplicationCacheForWrite(nsIApplicationCache *appCache) michael@0: { michael@0: ENSURE_CALLED_BEFORE_CONNECT(); michael@0: michael@0: mApplicationCacheForWrite = appCache; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetLoadedFromApplicationCache(bool *aLoadedFromApplicationCache) michael@0: { michael@0: *aLoadedFromApplicationCache = mLoadedFromApplicationCache; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetInheritApplicationCache(bool *aInherit) michael@0: { michael@0: *aInherit = mInheritApplicationCache; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::SetInheritApplicationCache(bool aInherit) michael@0: { michael@0: ENSURE_CALLED_BEFORE_CONNECT(); michael@0: michael@0: mInheritApplicationCache = aInherit; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::GetChooseApplicationCache(bool *aChoose) michael@0: { michael@0: *aChoose = mChooseApplicationCache; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::SetChooseApplicationCache(bool aChoose) michael@0: { michael@0: ENSURE_CALLED_BEFORE_CONNECT(); michael@0: michael@0: mChooseApplicationCache = aChoose; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsHttpChannel::OfflineCacheEntryAsForeignMarker* michael@0: nsHttpChannel::GetOfflineCacheEntryAsForeignMarker() michael@0: { michael@0: if (!mApplicationCache) michael@0: return nullptr; michael@0: michael@0: return new OfflineCacheEntryAsForeignMarker(mApplicationCache, mURI); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpChannel::OfflineCacheEntryAsForeignMarker::MarkAsForeign() michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr noRefURI; michael@0: rv = mCacheURI->CloneIgnoringRef(getter_AddRefs(noRefURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsAutoCString spec; michael@0: rv = noRefURI->GetAsciiSpec(spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return mApplicationCache->MarkEntry(spec, michael@0: nsIApplicationCache::ITEM_FOREIGN); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::MarkOfflineCacheEntryAsForeign() michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsAutoPtr marker( michael@0: GetOfflineCacheEntryAsForeignMarker()); michael@0: michael@0: if (!marker) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: rv = marker->MarkAsForeign(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel::nsIAsyncVerifyRedirectCallback michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: nsHttpChannel::WaitForRedirectCallback() michael@0: { michael@0: nsresult rv; michael@0: LOG(("nsHttpChannel::WaitForRedirectCallback [this=%p]\n", this)); michael@0: michael@0: if (mTransactionPump) { michael@0: rv = mTransactionPump->Suspend(); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: if (mCachePump) { michael@0: rv = mCachePump->Suspend(); michael@0: if (NS_FAILED(rv) && mTransactionPump) { michael@0: #ifdef DEBUG michael@0: nsresult resume = michael@0: #endif michael@0: mTransactionPump->Resume(); michael@0: MOZ_ASSERT(NS_SUCCEEDED(resume), michael@0: "Failed to resume transaction pump"); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: mWaitingForRedirectCallback = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::OnRedirectVerifyCallback(nsresult result) michael@0: { michael@0: LOG(("nsHttpChannel::OnRedirectVerifyCallback [this=%p] " michael@0: "result=%x stack=%d mWaitingForRedirectCallback=%u\n", michael@0: this, result, mRedirectFuncStack.Length(), mWaitingForRedirectCallback)); michael@0: MOZ_ASSERT(mWaitingForRedirectCallback, michael@0: "Someone forgot to call WaitForRedirectCallback() ?!"); michael@0: mWaitingForRedirectCallback = false; michael@0: michael@0: if (mCanceled && NS_SUCCEEDED(result)) michael@0: result = NS_BINDING_ABORTED; michael@0: michael@0: for (uint32_t i = mRedirectFuncStack.Length(); i > 0;) { michael@0: --i; michael@0: // Pop the last function pushed to the stack michael@0: nsContinueRedirectionFunc func = mRedirectFuncStack[i]; michael@0: mRedirectFuncStack.RemoveElementAt(mRedirectFuncStack.Length() - 1); michael@0: michael@0: // Call it with the result we got from the callback or the deeper michael@0: // function call. michael@0: result = (this->*func)(result); michael@0: michael@0: // If a new function has been pushed to the stack and placed us in the michael@0: // waiting state, we need to break the chain and wait for the callback michael@0: // again. michael@0: if (mWaitingForRedirectCallback) michael@0: break; michael@0: } michael@0: michael@0: if (NS_FAILED(result) && !mCanceled) { michael@0: // First, cancel this channel if we are in failure state to set mStatus michael@0: // and let it be propagated to pumps. michael@0: Cancel(result); michael@0: } michael@0: michael@0: if (!mWaitingForRedirectCallback) { michael@0: // We are not waiting for the callback. At this moment we must release michael@0: // reference to the redirect target channel, otherwise we may leak. michael@0: mRedirectChannel = nullptr; michael@0: MOZ_EVENT_TRACER_DONE(this, "net::http::channel"); michael@0: } michael@0: michael@0: // We always resume the pumps here. If all functions on stack have been michael@0: // called we need OnStopRequest to be triggered, and if we broke out of the michael@0: // loop above (and are thus waiting for a new callback) the suspension michael@0: // count must be balanced in the pumps. michael@0: if (mTransactionPump) michael@0: mTransactionPump->Resume(); michael@0: if (mCachePump) michael@0: mCachePump->Resume(); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::PushRedirectAsyncFunc(nsContinueRedirectionFunc func) michael@0: { michael@0: mRedirectFuncStack.AppendElement(func); michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::PopRedirectAsyncFunc(nsContinueRedirectionFunc func) michael@0: { michael@0: MOZ_ASSERT(func == mRedirectFuncStack[mRedirectFuncStack.Length() - 1], michael@0: "Trying to pop wrong method from redirect async stack!"); michael@0: michael@0: mRedirectFuncStack.TruncateLength(mRedirectFuncStack.Length() - 1); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsIDNSListener functions michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::OnLookupComplete(nsICancelable *request, michael@0: nsIDNSRecord *rec, michael@0: nsresult status) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Expecting DNS callback on main thread."); michael@0: michael@0: LOG(("nsHttpChannel::OnLookupComplete [this=%p] prefetch complete%s: " michael@0: "%s status[0x%x]\n", michael@0: this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : "", michael@0: NS_SUCCEEDED(status) ? "success" : "failure", status)); michael@0: michael@0: // We no longer need the dns prefetch object. Note: mDNSPrefetch could be michael@0: // validly null if OnStopRequest has already been called. michael@0: if (mDNSPrefetch && mDNSPrefetch->TimingsValid()) { michael@0: mTransactionTimings.domainLookupStart = michael@0: mDNSPrefetch->StartTimestamp(); michael@0: mTransactionTimings.domainLookupEnd = michael@0: mDNSPrefetch->EndTimestamp(); michael@0: } michael@0: mDNSPrefetch = nullptr; michael@0: michael@0: // Unset DNS cache refresh if it was requested, michael@0: if (mCaps & NS_HTTP_REFRESH_DNS) { michael@0: mCaps &= ~NS_HTTP_REFRESH_DNS; michael@0: if (mTransaction) { michael@0: mTransaction->SetDNSWasRefreshed(); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpChannel internal functions michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: void michael@0: nsHttpChannel::MaybeInvalidateCacheEntryForSubsequentGet() michael@0: { michael@0: // See RFC 2616 section 5.1.1. These are considered valid michael@0: // methods which DO NOT invalidate cache-entries for the michael@0: // referred resource. POST, PUT and DELETE as well as any michael@0: // other method not listed here will potentially invalidate michael@0: // any cached copy of the resource michael@0: if (mRequestHead.IsGet() || mRequestHead.IsOptions() || michael@0: mRequestHead.IsHead() || mRequestHead.IsTrace() || michael@0: mRequestHead.IsConnect()) { michael@0: return; michael@0: } michael@0: michael@0: // Invalidate the request-uri. michael@0: #ifdef PR_LOGGING michael@0: nsAutoCString key; michael@0: mURI->GetAsciiSpec(key); michael@0: LOG(("MaybeInvalidateCacheEntryForSubsequentGet [this=%p uri=%s]\n", michael@0: this, key.get())); michael@0: #endif michael@0: michael@0: DoInvalidateCacheEntry(mURI); michael@0: michael@0: // Invalidate Location-header if set michael@0: const char *location = mResponseHead->PeekHeader(nsHttp::Location); michael@0: if (location) { michael@0: LOG((" Location-header=%s\n", location)); michael@0: InvalidateCacheEntryForLocation(location); michael@0: } michael@0: michael@0: // Invalidate Content-Location-header if set michael@0: location = mResponseHead->PeekHeader(nsHttp::Content_Location); michael@0: if (location) { michael@0: LOG((" Content-Location-header=%s\n", location)); michael@0: InvalidateCacheEntryForLocation(location); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::InvalidateCacheEntryForLocation(const char *location) michael@0: { michael@0: nsAutoCString tmpCacheKey, tmpSpec; michael@0: nsCOMPtr resultingURI; michael@0: nsresult rv = CreateNewURI(location, getter_AddRefs(resultingURI)); michael@0: if (NS_SUCCEEDED(rv) && HostPartIsTheSame(resultingURI)) { michael@0: DoInvalidateCacheEntry(resultingURI); michael@0: } else { michael@0: LOG((" hosts not matching\n")); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::DoInvalidateCacheEntry(nsIURI* aURI) michael@0: { michael@0: // NOTE: michael@0: // Following comments 24,32 and 33 in bug #327765, we only care about michael@0: // the cache in the protocol-handler, not the application cache. michael@0: // The logic below deviates from the original logic in OpenCacheEntry on michael@0: // one point by using only READ_ONLY access-policy. I think this is safe. michael@0: michael@0: nsresult rv; michael@0: michael@0: #ifdef PR_LOGGING michael@0: nsAutoCString key; michael@0: aURI->GetAsciiSpec(key); michael@0: LOG(("DoInvalidateCacheEntry [channel=%p key=%s]", this, key.get())); michael@0: #endif michael@0: michael@0: nsCOMPtr cacheStorageService = michael@0: do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv); michael@0: michael@0: nsCOMPtr cacheStorage; michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsRefPtr info = GetLoadContextInfo(this); michael@0: rv = cacheStorageService->DiskCacheStorage(info, false, getter_AddRefs(cacheStorage)); michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = cacheStorage->AsyncDoomURI(aURI, EmptyCString(), nullptr); michael@0: } michael@0: michael@0: LOG(("DoInvalidateCacheEntry [channel=%p key=%s rv=%d]", this, key.get(), int(rv))); michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::AsyncOnExamineCachedResponse() michael@0: { michael@0: gHttpHandler->OnExamineCachedResponse(this); michael@0: michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::UpdateAggregateCallbacks() michael@0: { michael@0: if (!mTransaction) { michael@0: return; michael@0: } michael@0: nsCOMPtr callbacks; michael@0: NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup, michael@0: NS_GetCurrentThread(), michael@0: getter_AddRefs(callbacks)); michael@0: mTransaction->SetSecurityCallbacks(callbacks); michael@0: } michael@0: michael@0: nsIPrincipal * michael@0: nsHttpChannel::GetPrincipal() michael@0: { michael@0: if (mPrincipal) michael@0: return mPrincipal; michael@0: michael@0: nsIScriptSecurityManager *securityManager = michael@0: nsContentUtils::GetSecurityManager(); michael@0: michael@0: if (!securityManager) michael@0: return nullptr; michael@0: michael@0: securityManager->GetChannelPrincipal(this, getter_AddRefs(mPrincipal)); michael@0: if (!mPrincipal) michael@0: return nullptr; michael@0: michael@0: // principals with unknown app ids do not work with the permission manager michael@0: if (mPrincipal->GetUnknownAppId()) michael@0: mPrincipal = nullptr; michael@0: michael@0: return mPrincipal; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::SetLoadGroup(nsILoadGroup *aLoadGroup) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread."); michael@0: michael@0: nsresult rv = HttpBaseChannel::SetLoadGroup(aLoadGroup); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: UpdateAggregateCallbacks(); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks) michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "Wrong thread."); michael@0: michael@0: nsresult rv = HttpBaseChannel::SetNotificationCallbacks(aCallbacks); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: UpdateAggregateCallbacks(); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsPerformance* michael@0: nsHttpChannel::GetPerformance() michael@0: { michael@0: // If performance timing is disabled, there is no need for the nsPerformance michael@0: // object anymore. michael@0: if (!mTimingEnabled) { michael@0: return nullptr; michael@0: } michael@0: nsCOMPtr loadContext; michael@0: NS_QueryNotificationCallbacks(this, loadContext); michael@0: if (!loadContext) { michael@0: return nullptr; michael@0: } michael@0: nsCOMPtr domWindow; michael@0: loadContext->GetAssociatedWindow(getter_AddRefs(domWindow)); michael@0: if (!domWindow) { michael@0: return nullptr; michael@0: } michael@0: nsCOMPtr pDomWindow = do_QueryInterface(domWindow); michael@0: if (!pDomWindow) { michael@0: return nullptr; michael@0: } michael@0: if (!pDomWindow->IsInnerWindow()) { michael@0: pDomWindow = pDomWindow->GetCurrentInnerWindow(); michael@0: if (!pDomWindow) { michael@0: return nullptr; michael@0: } michael@0: } michael@0: michael@0: nsPerformance* docPerformance = pDomWindow->GetPerformance(); michael@0: if (!docPerformance) { michael@0: return nullptr; michael@0: } michael@0: // iframes should be added to the parent's entries list. michael@0: if (mLoadFlags & LOAD_DOCUMENT_URI) { michael@0: return docPerformance->GetParentPerformance(); michael@0: } michael@0: return docPerformance; michael@0: } michael@0: michael@0: void michael@0: nsHttpChannel::ForcePending(bool aForcePending) michael@0: { michael@0: // Set true here so IsPending will return true. michael@0: // Required for callback diversion from child back to parent. In such cases michael@0: // OnStopRequest can be called in the parent before callbacks are diverted michael@0: // back from the child to the listener in the parent. michael@0: mForcePending = aForcePending; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHttpChannel::IsPending(bool *aIsPending) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aIsPending); michael@0: *aIsPending = mIsPending || mForcePending; michael@0: return NS_OK; michael@0: } michael@0: michael@0: } } // namespace mozilla::net