diff options
Diffstat (limited to '')
-rw-r--r-- | netwerk/protocol/http/HttpChannelChild.cpp | 3176 |
1 files changed, 3176 insertions, 0 deletions
diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp new file mode 100644 index 0000000000..34e7a2cf0d --- /dev/null +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -0,0 +1,3176 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// HttpLog.h should generally be included first +#include "HttpLog.h" + +#include "nsHttp.h" +#include "nsICacheEntry.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/PerfStats.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/ServiceWorkerUtils.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/extensions/StreamFilterParent.h" +#include "mozilla/ipc/IPCStreamUtils.h" +#include "mozilla/net/NeckoChild.h" +#include "mozilla/net/HttpChannelChild.h" +#include "mozilla/net/UrlClassifierCommon.h" +#include "mozilla/net/UrlClassifierFeatureFactory.h" + +#include "AltDataOutputStreamChild.h" +#include "CookieServiceChild.h" +#include "HttpBackgroundChannelChild.h" +#include "NetworkMarker.h" +#include "nsCOMPtr.h" +#include "nsContentPolicyUtils.h" +#include "nsDOMNavigationTiming.h" +#include "nsGlobalWindow.h" +#include "nsStringStream.h" +#include "nsHttpChannel.h" +#include "nsHttpHandler.h" +#include "nsQueryObject.h" +#include "nsNetUtil.h" +#include "nsSerializationHelper.h" +#include "mozilla/Attributes.h" +#include "mozilla/Telemetry.h" +#include "mozilla/dom/PerformanceStorage.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/net/DNS.h" +#include "mozilla/net/SocketProcessBridgeChild.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StaticPrefs_network.h" +#include "mozilla/StoragePrincipalHelper.h" +#include "SerializedLoadContext.h" +#include "nsInputStreamPump.h" +#include "nsContentSecurityManager.h" +#include "nsICompressConvStats.h" +#include "mozilla/dom/Document.h" +#include "nsIScriptError.h" +#include "nsISerialEventTarget.h" +#include "nsRedirectHistoryEntry.h" +#include "nsSocketTransportService2.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" +#include "nsCORSListenerProxy.h" +#include "nsIOService.h" + +#include <functional> + +using namespace mozilla::dom; +using namespace mozilla::ipc; + +namespace mozilla::net { + +//----------------------------------------------------------------------------- +// HttpChannelChild +//----------------------------------------------------------------------------- + +HttpChannelChild::HttpChannelChild() + : HttpAsyncAborter<HttpChannelChild>(this), + NeckoTargetHolder(nullptr), + mCacheEntryAvailable(false), + mAltDataCacheEntryAvailable(false), + mSendResumeAt(false), + mKeptAlive(false), + mIPCActorDeleted(false), + mSuspendSent(false), + mIsFirstPartOfMultiPart(false), + mIsLastPartOfMultiPart(false), + mSuspendForWaitCompleteRedirectSetup(false), + mRecvOnStartRequestSentCalled(false), + mSuspendedByWaitingForPermissionCookie(false) { + LOG(("Creating HttpChannelChild @%p\n", this)); + + mChannelCreationTime = PR_Now(); + mChannelCreationTimestamp = TimeStamp::Now(); + mLastStatusReported = + mChannelCreationTimestamp; // in case we enable the profiler after Init() + mAsyncOpenTime = TimeStamp::Now(); + mEventQ = new ChannelEventQueue(static_cast<nsIHttpChannel*>(this)); + + // Ensure that the cookie service is initialized before the first + // IPC HTTP channel is created. + // We require that the parent cookie service actor exists while + // processing HTTP responses. + RefPtr<CookieServiceChild> cookieService = CookieServiceChild::GetSingleton(); +} + +HttpChannelChild::~HttpChannelChild() { + LOG(("Destroying HttpChannelChild @%p\n", this)); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + // See HttpChannelChild::Release, HttpChannelChild should be always destroyed + // on the main thread. + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + + if (mDoDiagnosticAssertWhenOnStopNotCalledOnDestroy && mAsyncOpenSucceeded && + !mSuccesfullyRedirected && !LoadOnStopRequestCalled()) { + bool emptyBgChildQueue, nullBgChild; + { + MutexAutoLock lock(mBgChildMutex); + nullBgChild = !mBgChild; + emptyBgChildQueue = !nullBgChild && mBgChild->IsQueueEmpty(); + } + + uint32_t flags = + (mRedirectChannelChild ? 1 << 0 : 0) | + (mEventQ->IsEmpty() ? 1 << 1 : 0) | (nullBgChild ? 1 << 2 : 0) | + (emptyBgChildQueue ? 1 << 3 : 0) | + (LoadOnStartRequestCalled() ? 1 << 4 : 0) | + (mBackgroundChildQueueFinalState == BCKCHILD_EMPTY ? 1 << 5 : 0) | + (mBackgroundChildQueueFinalState == BCKCHILD_NON_EMPTY ? 1 << 6 : 0) | + (mRemoteChannelExistedAtCancel ? 1 << 7 : 0) | + (mEverHadBgChildAtAsyncOpen ? 1 << 8 : 0) | + (mEverHadBgChildAtConnectParent ? 1 << 9 : 0) | + (mCreateBackgroundChannelFailed ? 1 << 10 : 0) | + (mBgInitFailCallbackTriggered ? 1 << 11 : 0) | + (mCanSendAtCancel ? 1 << 12 : 0) | (!!mSuspendCount ? 1 << 13 : 0) | + (!!mCallOnResume ? 1 << 14 : 0); + MOZ_CRASH_UNSAFE_PRINTF( + "~HttpChannelChild, LoadOnStopRequestCalled()=false, mStatus=0x%08x, " + "mActorDestroyReason=%d, 20200717 flags=%u", + static_cast<uint32_t>(nsresult(mStatus)), + static_cast<int32_t>(mActorDestroyReason ? *mActorDestroyReason : -1), + flags); + } +#endif + + mEventQ->NotifyReleasingOwner(); + + ReleaseMainThreadOnlyReferences(); +} + +void HttpChannelChild::ReleaseMainThreadOnlyReferences() { + if (NS_IsMainThread()) { + // Already on main thread, let dtor to + // take care of releasing references + return; + } + + NS_ReleaseOnMainThread("HttpChannelChild::mRedirectChannelChild", + mRedirectChannelChild.forget()); +} +//----------------------------------------------------------------------------- +// HttpChannelChild::nsISupports +//----------------------------------------------------------------------------- + +NS_IMPL_ADDREF(HttpChannelChild) + +NS_IMETHODIMP_(MozExternalRefCountType) HttpChannelChild::Release() { + if (!NS_IsMainThread()) { + nsrefcnt count = mRefCnt; + nsresult rv = NS_DispatchToMainThread(NewNonOwningRunnableMethod( + "HttpChannelChild::Release", this, &HttpChannelChild::Release)); + + // Continue Release procedure if failed to dispatch to main thread. + if (!NS_WARN_IF(NS_FAILED(rv))) { + return count - 1; + } + } + + nsrefcnt count = --mRefCnt; + MOZ_ASSERT(int32_t(count) >= 0, "dup release"); + + // Normally we Send_delete in OnStopRequest, but when we need to retain the + // remote channel for security info IPDL itself holds 1 reference, so we + // Send_delete when refCnt==1. But if !CanSend(), then there's nobody to send + // to, so we fall through. + if (mKeptAlive && count == 1 && CanSend()) { + NS_LOG_RELEASE(this, 1, "HttpChannelChild"); + mKeptAlive = false; + // We send a message to the parent, which calls SendDelete, and then the + // child calling Send__delete__() to finally drop the refcount to 0. + TrySendDeletingChannel(); + return 1; + } + + if (count == 0) { + mRefCnt = 1; /* stabilize */ + + // We don't have a listener when AsyncOpen has failed or when this channel + // has been sucessfully redirected. + if (MOZ_LIKELY(LoadOnStartRequestCalled() && LoadOnStopRequestCalled()) || + !mListener) { + NS_LOG_RELEASE(this, 0, "HttpChannelChild"); + delete this; + return 0; + } + + // This makes sure we fulfill the stream listener contract all the time. + if (NS_SUCCEEDED(mStatus)) { + mStatus = NS_ERROR_ABORT; + } + + // Turn the stabilization refcount into a regular strong reference. + + // 1) We tell refcount logging about the "stabilization" AddRef, which + // will become the reference for |channel|. We do this first so that we + // don't tell refcount logging that the refcount has dropped to zero, which + // it will interpret as destroying the object. + NS_LOG_ADDREF(this, 2, "HttpChannelChild", sizeof(*this)); + + // 2) We tell refcount logging about the original call to Release(). + NS_LOG_RELEASE(this, 1, "HttpChannelChild"); + + // 3) Finally, we turn the reference into a regular smart pointer. + RefPtr<HttpChannelChild> channel = dont_AddRef(this); + + // This runnable will create a strong reference to |this|. + NS_DispatchToMainThread( + NewRunnableMethod("~HttpChannelChild>DoNotifyListener", channel, + &HttpChannelChild::DoNotifyListener)); + + // If NS_DispatchToMainThread failed then we're going to leak the runnable, + // and thus the channel, so there's no need to do anything else. + + // We should have already done any special handling for the refcount = 1 + // case when the refcount first went from 2 to 1. We don't want it to happen + // when |channel| is destroyed. + MOZ_ASSERT(!mKeptAlive || !CanSend()); + + // XXX If std::move(channel) is allowed, then we don't have to have extra + // checks for the refcount going from 2 to 1. See bug 1680217. + + // This will release the stabilization refcount, which is necessary to avoid + // a leak. + channel = nullptr; + + return mRefCnt; + } + + NS_LOG_RELEASE(this, count, "HttpChannelChild"); + return count; +} + +NS_INTERFACE_MAP_BEGIN(HttpChannelChild) + NS_INTERFACE_MAP_ENTRY(nsIRequest) + NS_INTERFACE_MAP_ENTRY(nsIChannel) + NS_INTERFACE_MAP_ENTRY(nsIHttpChannel) + NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICacheInfoChannel, + !mMultiPartID.isSome()) + NS_INTERFACE_MAP_ENTRY(nsIResumableChannel) + NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) + NS_INTERFACE_MAP_ENTRY(nsIClassOfService) + NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel) + NS_INTERFACE_MAP_ENTRY(nsITraceableChannel) + NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback) + NS_INTERFACE_MAP_ENTRY(nsIChildChannel) + NS_INTERFACE_MAP_ENTRY(nsIHttpChannelChild) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIMultiPartChannel, mMultiPartID.isSome()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIThreadRetargetableRequest, + !mMultiPartID.isSome()) + NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpChannelChild) +NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel) + +//----------------------------------------------------------------------------- +// HttpChannelChild::PHttpChannelChild +//----------------------------------------------------------------------------- + +void HttpChannelChild::OnBackgroundChildReady( + HttpBackgroundChannelChild* aBgChild) { + LOG(("HttpChannelChild::OnBackgroundChildReady [this=%p, bgChild=%p]\n", this, + aBgChild)); + MOZ_ASSERT(OnSocketThread()); + + { + MutexAutoLock lock(mBgChildMutex); + + // mBgChild might be removed or replaced while the original background + // channel is inited on STS thread. + if (mBgChild != aBgChild) { + return; + } + + MOZ_ASSERT(mBgInitFailCallback); + mBgInitFailCallback = nullptr; + } +} + +void HttpChannelChild::OnBackgroundChildDestroyed( + HttpBackgroundChannelChild* aBgChild) { + LOG(("HttpChannelChild::OnBackgroundChildDestroyed [this=%p]\n", this)); + // This function might be called during shutdown phase, so OnSocketThread() + // might return false even on STS thread. Use IsOnCurrentThreadInfallible() + // to get correct information. + MOZ_ASSERT(gSocketTransportService); + MOZ_ASSERT(gSocketTransportService->IsOnCurrentThreadInfallible()); + + nsCOMPtr<nsIRunnable> callback; + { + MutexAutoLock lock(mBgChildMutex); + + // mBgChild might be removed or replaced while the original background + // channel is destroyed on STS thread. + if (aBgChild != mBgChild) { + return; + } + + mBgChild = nullptr; + callback = std::move(mBgInitFailCallback); + } + + if (callback) { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mBgInitFailCallbackTriggered = true; +#endif + nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget(); + neckoTarget->Dispatch(callback, NS_DISPATCH_NORMAL); + } +} + +mozilla::ipc::IPCResult HttpChannelChild::RecvOnStartRequestSent() { + LOG(("HttpChannelChild::RecvOnStartRequestSent [this=%p]\n", this)); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mRecvOnStartRequestSentCalled); + + mRecvOnStartRequestSentCalled = true; + + if (mSuspendedByWaitingForPermissionCookie) { + mSuspendedByWaitingForPermissionCookie = false; + mEventQ->Resume(); + } + return IPC_OK(); +} + +void HttpChannelChild::ProcessOnStartRequest( + const nsHttpResponseHead& aResponseHead, const bool& aUseResponseHead, + const nsHttpHeaderArray& aRequestHeaders, + const HttpChannelOnStartRequestArgs& aArgs, + const HttpChannelAltDataStream& aAltData) { + LOG(("HttpChannelChild::ProcessOnStartRequest [this=%p]\n", this)); + MOZ_ASSERT(OnSocketThread()); + +#ifdef NIGHTLY_BUILD + TimeStamp start = TimeStamp::Now(); +#endif + + mAltDataInputStream = DeserializeIPCStream(aAltData.altDataInputStream()); + + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, [self = UnsafePtr<HttpChannelChild>(this), aResponseHead, +#ifdef NIGHTLY_BUILD + aUseResponseHead, aRequestHeaders, aArgs, start]() { + if (self->mLoadFlags & nsIRequest::LOAD_RECORD_START_REQUEST_DELAY) { + TimeDuration delay = TimeStamp::Now() - start; + Telemetry::Accumulate( + Telemetry::HTTP_PRELOAD_IMAGE_STARTREQUEST_DELAY, + static_cast<uint32_t>(delay.ToMilliseconds())); + } +#else + aUseResponseHead, aRequestHeaders, aArgs]() { +#endif + + self->OnStartRequest(aResponseHead, aUseResponseHead, aRequestHeaders, + aArgs); + })); +} + +static void ResourceTimingStructArgsToTimingsStruct( + const ResourceTimingStructArgs& aArgs, TimingStruct& aTimings) { + aTimings.domainLookupStart = aArgs.domainLookupStart(); + aTimings.domainLookupEnd = aArgs.domainLookupEnd(); + aTimings.connectStart = aArgs.connectStart(); + aTimings.tcpConnectEnd = aArgs.tcpConnectEnd(); + aTimings.secureConnectionStart = aArgs.secureConnectionStart(); + aTimings.connectEnd = aArgs.connectEnd(); + aTimings.requestStart = aArgs.requestStart(); + aTimings.responseStart = aArgs.responseStart(); + aTimings.responseEnd = aArgs.responseEnd(); +} + +void HttpChannelChild::OnStartRequest( + const nsHttpResponseHead& aResponseHead, const bool& aUseResponseHead, + const nsHttpHeaderArray& aRequestHeaders, + const HttpChannelOnStartRequestArgs& aArgs) { + LOG(("HttpChannelChild::OnStartRequest [this=%p]\n", this)); + + // If this channel was aborted by ActorDestroy, then there may be other + // OnStartRequest/OnStopRequest/OnDataAvailable IPC messages that need to + // be handled. In that case we just ignore them to avoid calling the listener + // twice. + if (LoadOnStartRequestCalled() && mIPCActorDeleted) { + return; + } + + // Copy arguments only. It's possible to handle other IPC between + // OnStartRequest and DoOnStartRequest. + mComputedCrossOriginOpenerPolicy = aArgs.openerPolicy(); + + if (!mCanceled && NS_SUCCEEDED(mStatus)) { + mStatus = aArgs.channelStatus(); + } + + // Cookies headers should not be visible to the child process + MOZ_ASSERT(!aRequestHeaders.HasHeader(nsHttp::Cookie)); + MOZ_ASSERT(!nsHttpResponseHead(aResponseHead).HasHeader(nsHttp::Set_Cookie)); + + if (aUseResponseHead && !mCanceled) { + mResponseHead = MakeUnique<nsHttpResponseHead>(aResponseHead); + } + + mSecurityInfo = aArgs.securityInfo(); + + ipc::MergeParentLoadInfoForwarder(aArgs.loadInfoForwarder(), mLoadInfo); + + mIsFromCache = aArgs.isFromCache(); + mIsRacing = aArgs.isRacing(); + mCacheEntryAvailable = aArgs.cacheEntryAvailable(); + mCacheEntryId = aArgs.cacheEntryId(); + mCacheFetchCount = aArgs.cacheFetchCount(); + mCacheExpirationTime = aArgs.cacheExpirationTime(); + mSelfAddr = aArgs.selfAddr(); + mPeerAddr = aArgs.peerAddr(); + + mRedirectCount = aArgs.redirectCount(); + mAvailableCachedAltDataType = aArgs.altDataType(); + StoreDeliveringAltData(aArgs.deliveringAltData()); + mAltDataLength = aArgs.altDataLength(); + StoreResolvedByTRR(aArgs.isResolvedByTRR()); + mEffectiveTRRMode = aArgs.effectiveTRRMode(); + mTRRSkipReason = aArgs.trrSkipReason(); + + SetApplyConversion(aArgs.applyConversion()); + + StoreAfterOnStartRequestBegun(true); + StoreHasHTTPSRR(aArgs.hasHTTPSRR()); + + AutoEventEnqueuer ensureSerialDispatch(mEventQ); + + mCacheKey = aArgs.cacheKey(); + + StoreIsProxyUsed(aArgs.isProxyUsed()); + + // replace our request headers with what actually got sent in the parent + mRequestHead.SetHeaders(aRequestHeaders); + + // Note: this is where we would notify "http-on-examine-response" observers. + // We have deliberately disabled this for child processes (see bug 806753) + // + // gHttpHandler->OnExamineResponse(this); + + ResourceTimingStructArgsToTimingsStruct(aArgs.timing(), mTransactionTimings); + + nsAutoCString cosString; + ClassOfService::ToString(mClassOfService, cosString); + if (!mAsyncOpenTime.IsNull() && + !aArgs.timing().transactionPending().IsNull()) { + Telemetry::AccumulateTimeDelta( + Telemetry::NETWORK_ASYNC_OPEN_CHILD_TO_TRANSACTION_PENDING_EXP_MS, + cosString, mAsyncOpenTime, aArgs.timing().transactionPending()); + PerfStats::RecordMeasurement( + PerfStats::Metric::HttpChannelAsyncOpenToTransactionPending, + aArgs.timing().transactionPending() - mAsyncOpenTime); + } + + if (!aArgs.timing().responseStart().IsNull()) { + Telemetry::AccumulateTimeDelta( + Telemetry::NETWORK_RESPONSE_START_PARENT_TO_CONTENT_EXP_MS, cosString, + aArgs.timing().responseStart(), TimeStamp::Now()); + PerfStats::RecordMeasurement( + PerfStats::Metric::HttpChannelResponseStartParentToContent, + TimeStamp::Now() - aArgs.timing().responseStart()); + } + + StoreAllRedirectsSameOrigin(aArgs.allRedirectsSameOrigin()); + + mMultiPartID = aArgs.multiPartID(); + mIsFirstPartOfMultiPart = aArgs.isFirstPartOfMultiPart(); + mIsLastPartOfMultiPart = aArgs.isLastPartOfMultiPart(); + + if (aArgs.overrideReferrerInfo()) { + // The arguments passed to SetReferrerInfoInternal here should mirror the + // arguments passed in + // nsHttpChannel::ReEvaluateReferrerAfterTrackingStatusIsKnown(), except for + // aRespectBeforeConnect which we pass false here since we're intentionally + // overriding the referrer after BeginConnect(). + Unused << SetReferrerInfoInternal(aArgs.overrideReferrerInfo(), false, true, + false); + } + + if (!aArgs.cookie().IsEmpty()) { + SetCookie(aArgs.cookie()); + } + + if (aArgs.shouldWaitForOnStartRequestSent() && + !mRecvOnStartRequestSentCalled) { + LOG((" > pending DoOnStartRequest until RecvOnStartRequestSent\n")); + MOZ_ASSERT(NS_IsMainThread()); + + mEventQ->Suspend(); + mSuspendedByWaitingForPermissionCookie = true; + mEventQ->PrependEvent(MakeUnique<NeckoTargetChannelFunctionEvent>( + this, [self = UnsafePtr<HttpChannelChild>(this)]() { + self->DoOnStartRequest(self); + })); + return; + } + + // Remember whether HTTP3 is supported + if (mResponseHead) { + mSupportsHTTP3 = + nsHttpHandler::IsHttp3SupportedByServer(mResponseHead.get()); + } + + DoOnStartRequest(this); +} + +void HttpChannelChild::ProcessOnAfterLastPart(const nsresult& aStatus) { + LOG(("HttpChannelChild::ProcessOnAfterLastPart [this=%p]\n", this)); + MOZ_ASSERT(OnSocketThread()); + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, [self = UnsafePtr<HttpChannelChild>(this), aStatus]() { + self->OnAfterLastPart(aStatus); + })); +} + +void HttpChannelChild::OnAfterLastPart(const nsresult& aStatus) { + if (LoadOnStopRequestCalled()) { + return; + } + StoreOnStopRequestCalled(true); + + // notify "http-on-stop-connect" observers + gHttpHandler->OnStopRequest(this); + + ReleaseListeners(); + + // If a preferred alt-data type was set, the parent would hold a reference to + // the cache entry in case the child calls openAlternativeOutputStream(). + // (see nsHttpChannel::OnStopRequest) + if (!mPreferredCachedAltDataTypes.IsEmpty()) { + mAltDataCacheEntryAvailable = mCacheEntryAvailable; + } + mCacheEntryAvailable = false; + + if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus); + CleanupBackgroundChannel(); + + if (mLoadFlags & LOAD_DOCUMENT_URI) { + // Keep IPDL channel open, but only for updating security info. + // If IPDL is already closed, then do nothing. + if (CanSend()) { + mKeptAlive = true; + SendDocumentChannelCleanup(true); + } + } else { + // The parent process will respond by sending a DeleteSelf message and + // making sure not to send any more messages after that. + TrySendDeletingChannel(); + } +} + +void HttpChannelChild::DoOnStartRequest(nsIRequest* aRequest) { + nsresult rv; + + LOG(("HttpChannelChild::DoOnStartRequest [this=%p]\n", this)); + + // We handle all the listener chaining before OnStartRequest at this moment. + // Prevent additional listeners being added to the chain after the request + // as started. + StoreTracingEnabled(false); + + // mListener could be null if the redirect setup is not completed. + MOZ_ASSERT(mListener || LoadOnStartRequestCalled()); + if (!mListener) { + Cancel(NS_ERROR_FAILURE); + return; + } + + if (mListener) { + nsCOMPtr<nsIStreamListener> listener(mListener); + StoreOnStartRequestCalled(true); + rv = listener->OnStartRequest(aRequest); + } else { + rv = NS_ERROR_UNEXPECTED; + } + StoreOnStartRequestCalled(true); + + if (NS_FAILED(rv)) { + CancelWithReason(rv, "HttpChannelChild listener->OnStartRequest failed"_ns); + return; + } + + nsCOMPtr<nsIStreamListener> listener; + rv = DoApplyContentConversions(mListener, getter_AddRefs(listener), nullptr); + if (NS_FAILED(rv)) { + CancelWithReason(rv, + "HttpChannelChild DoApplyContentConversions failed"_ns); + } else if (listener) { + mListener = listener; + mCompressListener = listener; + } +} + +void HttpChannelChild::ProcessOnTransportAndData( + const nsresult& aChannelStatus, const nsresult& aTransportStatus, + const uint64_t& aOffset, const uint32_t& aCount, const nsACString& aData) { + LOG(("HttpChannelChild::ProcessOnTransportAndData [this=%p]\n", this)); + MOZ_ASSERT(OnSocketThread()); + mEventQ->RunOrEnqueue(new ChannelFunctionEvent( + [self = UnsafePtr<HttpChannelChild>(this)]() { + return self->GetODATarget(); + }, + [self = UnsafePtr<HttpChannelChild>(this), aChannelStatus, + aTransportStatus, aOffset, aCount, aData = nsCString(aData)]() { + self->OnTransportAndData(aChannelStatus, aTransportStatus, aOffset, + aCount, aData); + })); +} + +void HttpChannelChild::OnTransportAndData(const nsresult& aChannelStatus, + const nsresult& aTransportStatus, + const uint64_t& aOffset, + const uint32_t& aCount, + const nsACString& aData) { + LOG(("HttpChannelChild::OnTransportAndData [this=%p]\n", this)); + + if (!mCanceled && NS_SUCCEEDED(mStatus)) { + mStatus = aChannelStatus; + } + + if (mCanceled || NS_FAILED(mStatus)) { + return; + } + + // Hold queue lock throughout all three calls, else we might process a later + // necko msg in between them. + AutoEventEnqueuer ensureSerialDispatch(mEventQ); + + int64_t progressMax; + if (NS_FAILED(GetContentLength(&progressMax))) { + progressMax = -1; + } + + const int64_t progress = aOffset + aCount; + + // OnTransportAndData will be run on retargeted thread if applicable, however + // OnStatus/OnProgress event can only be fired on main thread. We need to + // dispatch the status/progress event handling back to main thread with the + // appropriate event target for networking. + if (NS_IsMainThread()) { + DoOnStatus(this, aTransportStatus); + DoOnProgress(this, progress, progressMax); + } else { + RefPtr<HttpChannelChild> self = this; + nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget(); + MOZ_ASSERT(neckoTarget); + + DebugOnly<nsresult> rv = neckoTarget->Dispatch( + NS_NewRunnableFunction( + "net::HttpChannelChild::OnTransportAndData", + [self, aTransportStatus, progress, progressMax]() { + self->DoOnStatus(self, aTransportStatus); + self->DoOnProgress(self, progress, progressMax); + }), + NS_DISPATCH_NORMAL); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + + // OnDataAvailable + // + // NOTE: the OnDataAvailable contract requires the client to read all the data + // in the inputstream. This code relies on that ('data' will go away after + // this function). Apparently the previous, non-e10s behavior was to actually + // support only reading part of the data, allowing later calls to read the + // rest. + nsCOMPtr<nsIInputStream> stringStream; + nsresult rv = + NS_NewByteInputStream(getter_AddRefs(stringStream), + Span(aData).To(aCount), NS_ASSIGNMENT_DEPEND); + if (NS_FAILED(rv)) { + CancelWithReason(rv, "HttpChannelChild NS_NewByteInputStream failed"_ns); + return; + } + + DoOnDataAvailable(this, stringStream, aOffset, aCount); + stringStream->Close(); + + // TODO: Bug 1523916 backpressure needs to take into account if the data is + // coming from the main process or from the socket process via PBackground. + if (NeedToReportBytesRead()) { + mUnreportBytesRead += aCount; + if (mUnreportBytesRead >= gHttpHandler->SendWindowSize() >> 2) { + if (NS_IsMainThread()) { + Unused << SendBytesRead(mUnreportBytesRead); + } else { + // PHttpChannel connects to the main thread + RefPtr<HttpChannelChild> self = this; + int32_t bytesRead = mUnreportBytesRead; + nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget(); + MOZ_ASSERT(neckoTarget); + + DebugOnly<nsresult> rv = neckoTarget->Dispatch( + NS_NewRunnableFunction("net::HttpChannelChild::SendBytesRead", + [self, bytesRead]() { + Unused << self->SendBytesRead(bytesRead); + }), + NS_DISPATCH_NORMAL); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + mUnreportBytesRead = 0; + } + } +} + +bool HttpChannelChild::NeedToReportBytesRead() { + if (mCacheNeedToReportBytesReadInitialized) { + return mNeedToReportBytesRead; + } + + // Might notify parent for partial cache, and the IPC message is ignored by + // parent. + int64_t contentLength = -1; + if (gHttpHandler->SendWindowSize() == 0 || mIsFromCache || + NS_FAILED(GetContentLength(&contentLength)) || + contentLength < gHttpHandler->SendWindowSize()) { + mNeedToReportBytesRead = false; + } + + mCacheNeedToReportBytesReadInitialized = true; + return mNeedToReportBytesRead; +} + +void HttpChannelChild::DoOnStatus(nsIRequest* aRequest, nsresult status) { + LOG(("HttpChannelChild::DoOnStatus [this=%p]\n", this)); + MOZ_ASSERT(NS_IsMainThread()); + + if (mCanceled) return; + + // cache the progress sink so we don't have to query for it each time. + if (!mProgressSink) GetCallback(mProgressSink); + + // block status/progress after Cancel or OnStopRequest has been called, + // or if channel has LOAD_BACKGROUND set. + if (mProgressSink && NS_SUCCEEDED(mStatus) && LoadIsPending() && + !(mLoadFlags & LOAD_BACKGROUND)) { + nsAutoCString host; + mURI->GetHost(host); + mProgressSink->OnStatus(aRequest, status, + NS_ConvertUTF8toUTF16(host).get()); + } +} + +void HttpChannelChild::DoOnProgress(nsIRequest* aRequest, int64_t progress, + int64_t progressMax) { + LOG(("HttpChannelChild::DoOnProgress [this=%p]\n", this)); + MOZ_ASSERT(NS_IsMainThread()); + + if (mCanceled) return; + + // cache the progress sink so we don't have to query for it each time. + if (!mProgressSink) GetCallback(mProgressSink); + + // block status/progress after Cancel or OnStopRequest has been called, + // or if channel has LOAD_BACKGROUND set. + if (mProgressSink && NS_SUCCEEDED(mStatus) && LoadIsPending()) { + // OnProgress + // + if (progress > 0) { + mProgressSink->OnProgress(aRequest, progress, progressMax); + } + } +} + +void HttpChannelChild::DoOnDataAvailable(nsIRequest* aRequest, + nsIInputStream* aStream, + uint64_t aOffset, uint32_t aCount) { + AUTO_PROFILER_LABEL("HttpChannelChild::DoOnDataAvailable", NETWORK); + LOG(("HttpChannelChild::DoOnDataAvailable [this=%p]\n", this)); + if (mCanceled) return; + + if (mListener) { + nsCOMPtr<nsIStreamListener> listener(mListener); + nsresult rv = listener->OnDataAvailable(aRequest, aStream, aOffset, aCount); + if (NS_FAILED(rv)) { + CancelOnMainThread(rv, "HttpChannelChild OnDataAvailable failed"_ns); + } + } +} + +void HttpChannelChild::ProcessOnStopRequest( + const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming, + const nsHttpHeaderArray& aResponseTrailers, + nsTArray<ConsoleReportCollected>&& aConsoleReports, + bool aFromSocketProcess) { + LOG( + ("HttpChannelChild::ProcessOnStopRequest [this=%p, " + "aFromSocketProcess=%d]\n", + this, aFromSocketProcess)); + MOZ_ASSERT(OnSocketThread()); + + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, [self = UnsafePtr<HttpChannelChild>(this), aChannelStatus, aTiming, + aResponseTrailers, + consoleReports = CopyableTArray{aConsoleReports.Clone()}, + aFromSocketProcess]() mutable { + self->OnStopRequest(aChannelStatus, aTiming, aResponseTrailers); + if (!aFromSocketProcess) { + self->DoOnConsoleReport(std::move(consoleReports)); + self->ContinueOnStopRequest(); + } + })); +} + +void HttpChannelChild::ProcessOnConsoleReport( + nsTArray<ConsoleReportCollected>&& aConsoleReports) { + LOG(("HttpChannelChild::ProcessOnConsoleReport [this=%p]\n", this)); + MOZ_ASSERT(OnSocketThread()); + + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, + [self = UnsafePtr<HttpChannelChild>(this), + consoleReports = CopyableTArray{aConsoleReports.Clone()}]() mutable { + self->DoOnConsoleReport(std::move(consoleReports)); + self->ContinueOnStopRequest(); + })); +} + +void HttpChannelChild::DoOnConsoleReport( + nsTArray<ConsoleReportCollected>&& aConsoleReports) { + if (aConsoleReports.IsEmpty()) { + return; + } + + for (ConsoleReportCollected& report : aConsoleReports) { + if (report.propertiesFile() < + nsContentUtils::PropertiesFile::PropertiesFile_COUNT) { + AddConsoleReport(report.errorFlags(), report.category(), + nsContentUtils::PropertiesFile(report.propertiesFile()), + report.sourceFileURI(), report.lineNumber(), + report.columnNumber(), report.messageName(), + report.stringParams()); + } + } + MaybeFlushConsoleReports(); +} + +void HttpChannelChild::OnStopRequest( + const nsresult& aChannelStatus, const ResourceTimingStructArgs& aTiming, + const nsHttpHeaderArray& aResponseTrailers) { + LOG(("HttpChannelChild::OnStopRequest [this=%p status=%" PRIx32 "]\n", this, + static_cast<uint32_t>(aChannelStatus))); + MOZ_ASSERT(NS_IsMainThread()); + + // If this channel was aborted by ActorDestroy, then there may be other + // OnStartRequest/OnStopRequest/OnDataAvailable IPC messages that need to + // be handled. In that case we just ignore them to avoid calling the listener + // twice. + if (LoadOnStopRequestCalled() && mIPCActorDeleted) { + return; + } + + nsCOMPtr<nsICompressConvStats> conv = do_QueryInterface(mCompressListener); + if (conv) { + conv->GetDecodedDataLength(&mDecodedBodySize); + } + + ResourceTimingStructArgsToTimingsStruct(aTiming, mTransactionTimings); + + // Do not overwrite or adjust the original mAsyncOpenTime by timing.fetchStart + // We must use the original child process time in order to account for child + // side work and IPC transit overhead. + // XXX: This depends on TimeStamp being equivalent across processes. + // This is true for modern hardware but for older platforms it is not always + // true. + + mRedirectStartTimeStamp = aTiming.redirectStart(); + mRedirectEndTimeStamp = aTiming.redirectEnd(); + mTransferSize = aTiming.transferSize(); + mEncodedBodySize = aTiming.encodedBodySize(); + mProtocolVersion = aTiming.protocolVersion(); + + mCacheReadStart = aTiming.cacheReadStart(); + mCacheReadEnd = aTiming.cacheReadEnd(); + + if (profiler_thread_is_being_profiled_for_markers()) { + nsAutoCString requestMethod; + GetRequestMethod(requestMethod); + nsAutoCString contentType; + if (mResponseHead) { + mResponseHead->ContentType(contentType); + } + int32_t priority = PRIORITY_NORMAL; + GetPriority(&priority); + profiler_add_network_marker( + mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_STOP, + mLastStatusReported, TimeStamp::Now(), mTransferSize, kCacheUnknown, + mLoadInfo->GetInnerWindowID(), + mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0, + &mTransactionTimings, std::move(mSource), + Some(nsDependentCString(contentType.get()))); + } + + TimeDuration channelCompletionDuration = TimeStamp::Now() - mAsyncOpenTime; + if (mIsFromCache) { + PerfStats::RecordMeasurement(PerfStats::Metric::HttpChannelCompletion_Cache, + channelCompletionDuration); + } else { + PerfStats::RecordMeasurement( + PerfStats::Metric::HttpChannelCompletion_Network, + channelCompletionDuration); + } + PerfStats::RecordMeasurement(PerfStats::Metric::HttpChannelCompletion, + channelCompletionDuration); + + if (!aTiming.responseEnd().IsNull()) { + nsAutoCString cosString; + ClassOfService::ToString(mClassOfService, cosString); + Telemetry::AccumulateTimeDelta( + Telemetry::NETWORK_RESPONSE_END_PARENT_TO_CONTENT_MS, cosString, + aTiming.responseEnd(), TimeStamp::Now()); + PerfStats::RecordMeasurement( + PerfStats::Metric::HttpChannelResponseEndParentToContent, + TimeStamp::Now() - aTiming.responseEnd()); + } + + mResponseTrailers = MakeUnique<nsHttpHeaderArray>(aResponseTrailers); + + DoPreOnStopRequest(aChannelStatus); + + { // We must flush the queue before we Send__delete__ + // (although we really shouldn't receive any msgs after OnStop), + // so make sure this goes out of scope before then. + AutoEventEnqueuer ensureSerialDispatch(mEventQ); + + DoOnStopRequest(this, aChannelStatus); + // DoOnStopRequest() calls ReleaseListeners() + } +} + +void HttpChannelChild::ContinueOnStopRequest() { + // If we're a multi-part stream, then don't cleanup yet, and we'll do so + // in OnAfterLastPart. + if (mMultiPartID) { + LOG( + ("HttpChannelChild::OnStopRequest - Expecting future parts on a " + "multipart channel postpone cleaning up.")); + return; + } + + CleanupBackgroundChannel(); + + // If there is a possibility we might want to write alt data to the cache + // entry, we keep the channel alive. We still send the DocumentChannelCleanup + // message but request the cache entry to be kept by the parent. + // If the channel has failed, the cache entry is in a non-writtable state and + // we want to release it to not block following consumers. + if (NS_SUCCEEDED(mStatus) && !mPreferredCachedAltDataTypes.IsEmpty()) { + mKeptAlive = true; + SendDocumentChannelCleanup(false); // don't clear cache entry + return; + } + + if (mLoadFlags & LOAD_DOCUMENT_URI) { + // Keep IPDL channel open, but only for updating security info. + // If IPDL is already closed, then do nothing. + if (CanSend()) { + mKeptAlive = true; + SendDocumentChannelCleanup(true); + } + } else { + // The parent process will respond by sending a DeleteSelf message and + // making sure not to send any more messages after that. + TrySendDeletingChannel(); + } +} + +void HttpChannelChild::DoPreOnStopRequest(nsresult aStatus) { + AUTO_PROFILER_LABEL("HttpChannelChild::DoPreOnStopRequest", NETWORK); + LOG(("HttpChannelChild::DoPreOnStopRequest [this=%p status=%" PRIx32 "]\n", + this, static_cast<uint32_t>(aStatus))); + StoreIsPending(false); + + MaybeReportTimingData(); + + if (!mCanceled && NS_SUCCEEDED(mStatus)) { + mStatus = aStatus; + } + + CollectOMTTelemetry(); +} + +void HttpChannelChild::CollectOMTTelemetry() { + MOZ_ASSERT(NS_IsMainThread()); + + // Only collect telemetry for HTTP channel that is loaded successfully and + // completely. + if (mCanceled || NS_FAILED(mStatus)) { + return; + } + + // Use content policy type to accumulate data by usage. + nsAutoCString key( + NS_CP_ContentTypeName(mLoadInfo->InternalContentPolicyType())); + + Telemetry::AccumulateCategoricalKeyed(key, mOMTResult); +} + +void HttpChannelChild::DoOnStopRequest(nsIRequest* aRequest, + nsresult aChannelStatus) { + AUTO_PROFILER_LABEL("HttpChannelChild::DoOnStopRequest", NETWORK); + LOG(("HttpChannelChild::DoOnStopRequest [this=%p]\n", this)); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!LoadIsPending()); + + auto checkForBlockedContent = [&]() { + // NB: We use aChannelStatus here instead of mStatus because if there was an + // nsCORSListenerProxy on this request, it will override the tracking + // protection's return value. + if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode( + aChannelStatus) || + aChannelStatus == NS_ERROR_MALWARE_URI || + aChannelStatus == NS_ERROR_UNWANTED_URI || + aChannelStatus == NS_ERROR_BLOCKED_URI || + aChannelStatus == NS_ERROR_HARMFUL_URI || + aChannelStatus == NS_ERROR_PHISHING_URI) { + nsCString list, provider, fullhash; + + nsresult rv = GetMatchedList(list); + NS_ENSURE_SUCCESS_VOID(rv); + + rv = GetMatchedProvider(provider); + NS_ENSURE_SUCCESS_VOID(rv); + + rv = GetMatchedFullHash(fullhash); + NS_ENSURE_SUCCESS_VOID(rv); + + UrlClassifierCommon::SetBlockedContent(this, aChannelStatus, list, + provider, fullhash); + } + }; + checkForBlockedContent(); + + MaybeLogCOEPError(aChannelStatus); + + // See bug 1587686. If the redirect setup is not completed, the post-redirect + // channel will be not opened and mListener will be null. + MOZ_ASSERT(mListener || !LoadWasOpened()); + if (!mListener) { + return; + } + + MOZ_ASSERT(!LoadOnStopRequestCalled(), + "We should not call OnStopRequest twice"); + + if (mListener) { + nsCOMPtr<nsIStreamListener> listener(mListener); + StoreOnStopRequestCalled(true); + listener->OnStopRequest(aRequest, mStatus); + } + StoreOnStopRequestCalled(true); + + // If we're a multi-part stream, then don't cleanup yet, and we'll do so + // in OnAfterLastPart. + if (mMultiPartID) { + LOG( + ("HttpChannelChild::DoOnStopRequest - Expecting future parts on a " + "multipart channel not releasing listeners.")); + StoreOnStopRequestCalled(false); + StoreOnStartRequestCalled(false); + return; + } + + // notify "http-on-stop-connect" observers + gHttpHandler->OnStopRequest(this); + + ReleaseListeners(); + + // If a preferred alt-data type was set, the parent would hold a reference to + // the cache entry in case the child calls openAlternativeOutputStream(). + // (see nsHttpChannel::OnStopRequest) + if (!mPreferredCachedAltDataTypes.IsEmpty()) { + mAltDataCacheEntryAvailable = mCacheEntryAvailable; + } + mCacheEntryAvailable = false; + + if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus); +} + +void HttpChannelChild::ProcessOnProgress(const int64_t& aProgress, + const int64_t& aProgressMax) { + MOZ_ASSERT(OnSocketThread()); + LOG(("HttpChannelChild::ProcessOnProgress [this=%p]\n", this)); + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, + [self = UnsafePtr<HttpChannelChild>(this), aProgress, aProgressMax]() { + AutoEventEnqueuer ensureSerialDispatch(self->mEventQ); + self->DoOnProgress(self, aProgress, aProgressMax); + })); +} + +void HttpChannelChild::ProcessOnStatus(const nsresult& aStatus) { + MOZ_ASSERT(OnSocketThread()); + LOG(("HttpChannelChild::ProcessOnStatus [this=%p]\n", this)); + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, [self = UnsafePtr<HttpChannelChild>(this), aStatus]() { + AutoEventEnqueuer ensureSerialDispatch(self->mEventQ); + self->DoOnStatus(self, aStatus); + })); +} + +mozilla::ipc::IPCResult HttpChannelChild::RecvFailedAsyncOpen( + const nsresult& aStatus) { + LOG(("HttpChannelChild::RecvFailedAsyncOpen [this=%p]\n", this)); + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, [self = UnsafePtr<HttpChannelChild>(this), aStatus]() { + self->FailedAsyncOpen(aStatus); + })); + return IPC_OK(); +} + +// We need to have an implementation of this function just so that we can keep +// all references to mCallOnResume of type HttpChannelChild: it's not OK in C++ +// to set a member function ptr to a base class function. +void HttpChannelChild::HandleAsyncAbort() { + HttpAsyncAborter<HttpChannelChild>::HandleAsyncAbort(); + + // Ignore all the messages from background channel after channel aborted. + CleanupBackgroundChannel(); +} + +void HttpChannelChild::FailedAsyncOpen(const nsresult& status) { + LOG(("HttpChannelChild::FailedAsyncOpen [this=%p status=%" PRIx32 "]\n", this, + static_cast<uint32_t>(status))); + MOZ_ASSERT(NS_IsMainThread()); + + // Might be called twice in race condition in theory. + // (one by RecvFailedAsyncOpen, another by + // HttpBackgroundChannelChild::ActorFailed) + if (LoadOnStartRequestCalled()) { + return; + } + + if (NS_SUCCEEDED(mStatus)) { + mStatus = status; + } + + // We're already being called from IPDL, therefore already "async" + HandleAsyncAbort(); + + if (CanSend()) { + TrySendDeletingChannel(); + } +} + +void HttpChannelChild::CleanupBackgroundChannel() { + MutexAutoLock lock(mBgChildMutex); + + AUTO_PROFILER_LABEL("HttpChannelChild::CleanupBackgroundChannel", NETWORK); + LOG(("HttpChannelChild::CleanupBackgroundChannel [this=%p bgChild=%p]\n", + this, mBgChild.get())); + + mBgInitFailCallback = nullptr; + + if (!mBgChild) { + return; + } + + RefPtr<HttpBackgroundChannelChild> bgChild = std::move(mBgChild); + + MOZ_RELEASE_ASSERT(gSocketTransportService); + if (!OnSocketThread()) { + gSocketTransportService->Dispatch( + NewRunnableMethod("HttpBackgroundChannelChild::OnChannelClosed", + bgChild, + &HttpBackgroundChannelChild::OnChannelClosed), + NS_DISPATCH_NORMAL); + } else { + bgChild->OnChannelClosed(); + } +} + +void HttpChannelChild::DoNotifyListenerCleanup() { + LOG(("HttpChannelChild::DoNotifyListenerCleanup [this=%p]\n", this)); +} + +void HttpChannelChild::DoAsyncAbort(nsresult aStatus) { + Unused << AsyncAbort(aStatus); +} + +mozilla::ipc::IPCResult HttpChannelChild::RecvDeleteSelf() { + LOG(("HttpChannelChild::RecvDeleteSelf [this=%p]\n", this)); + MOZ_ASSERT(NS_IsMainThread()); + + // The redirection is vetoed. No need to suspend the event queue. + if (mSuspendForWaitCompleteRedirectSetup) { + mSuspendForWaitCompleteRedirectSetup = false; + mEventQ->Resume(); + } + + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, + [self = UnsafePtr<HttpChannelChild>(this)]() { self->DeleteSelf(); })); + return IPC_OK(); +} + +void HttpChannelChild::DeleteSelf() { Send__delete__(this); } + +void HttpChannelChild::NotifyOrReleaseListeners(nsresult rv) { + MOZ_ASSERT(NS_IsMainThread()); + + if (NS_SUCCEEDED(rv) || + (LoadOnStartRequestCalled() && LoadOnStopRequestCalled())) { + ReleaseListeners(); + return; + } + + if (NS_SUCCEEDED(mStatus)) { + mStatus = rv; + } + + // This is enough what we need. Undelivered notifications will be pushed. + // DoNotifyListener ensures the call to ReleaseListeners when done. + DoNotifyListener(); +} + +void HttpChannelChild::DoNotifyListener() { + LOG(("HttpChannelChild::DoNotifyListener this=%p", this)); + MOZ_ASSERT(NS_IsMainThread()); + + // In case nsHttpChannel::OnStartRequest wasn't called (e.g. due to flag + // LOAD_ONLY_IF_MODIFIED) we want to set LoadAfterOnStartRequestBegun() to + // true before notifying listener. + if (!LoadAfterOnStartRequestBegun()) { + StoreAfterOnStartRequestBegun(true); + } + + if (mListener && !LoadOnStartRequestCalled()) { + nsCOMPtr<nsIStreamListener> listener = mListener; + StoreOnStartRequestCalled( + true); // avoid reentrancy bugs by setting this now + listener->OnStartRequest(this); + } + StoreOnStartRequestCalled(true); + + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, [self = UnsafePtr<HttpChannelChild>(this)] { + self->ContinueDoNotifyListener(); + })); +} + +void HttpChannelChild::ContinueDoNotifyListener() { + LOG(("HttpChannelChild::ContinueDoNotifyListener this=%p", this)); + MOZ_ASSERT(NS_IsMainThread()); + + // Make sure IsPending is set to false. At this moment we are done from + // the point of view of our consumer and we have to report our self + // as not-pending. + StoreIsPending(false); + + if (mListener && !LoadOnStopRequestCalled()) { + nsCOMPtr<nsIStreamListener> listener = mListener; + StoreOnStopRequestCalled(true); + listener->OnStopRequest(this, mStatus); + } + StoreOnStopRequestCalled(true); + + // notify "http-on-stop-request" observers + gHttpHandler->OnStopRequest(this); + + // This channel has finished its job, potentially release any tail-blocked + // requests with this. + RemoveAsNonTailRequest(); + + // We have to make sure to drop the references to listeners and callbacks + // no longer needed. + ReleaseListeners(); + + DoNotifyListenerCleanup(); + + // If this is a navigation, then we must let the docshell flush the reports + // to the console later. The LoadDocument() is pointing at the detached + // document that started the navigation. We want to show the reports on the + // new document. Otherwise the console is wiped and the user never sees + // the information. + if (!IsNavigation()) { + if (mLoadGroup) { + FlushConsoleReports(mLoadGroup); + } else { + RefPtr<dom::Document> doc; + mLoadInfo->GetLoadingDocument(getter_AddRefs(doc)); + FlushConsoleReports(doc); + } + } +} + +mozilla::ipc::IPCResult HttpChannelChild::RecvReportSecurityMessage( + const nsAString& messageTag, const nsAString& messageCategory) { + DebugOnly<nsresult> rv = AddSecurityMessage(messageTag, messageCategory); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult HttpChannelChild::RecvRedirect1Begin( + const uint32_t& aRegistrarId, nsIURI* aNewUri, + const uint32_t& aNewLoadFlags, const uint32_t& aRedirectFlags, + const ParentLoadInfoForwarderArgs& aLoadInfoForwarder, + const nsHttpResponseHead& aResponseHead, + nsITransportSecurityInfo* aSecurityInfo, const uint64_t& aChannelId, + const NetAddr& aOldPeerAddr, const ResourceTimingStructArgs& aTiming) { + // TODO: handle security info + LOG(("HttpChannelChild::RecvRedirect1Begin [this=%p]\n", this)); + // We set peer address of child to the old peer, + // Then it will be updated to new peer in OnStartRequest + mPeerAddr = aOldPeerAddr; + + // Cookies headers should not be visible to the child process + MOZ_ASSERT(!nsHttpResponseHead(aResponseHead).HasHeader(nsHttp::Set_Cookie)); + + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, [self = UnsafePtr<HttpChannelChild>(this), aRegistrarId, + newUri = RefPtr{aNewUri}, aNewLoadFlags, aRedirectFlags, + aLoadInfoForwarder, aResponseHead, + aSecurityInfo = nsCOMPtr{aSecurityInfo}, aChannelId, aTiming]() { + self->Redirect1Begin(aRegistrarId, newUri, aNewLoadFlags, + aRedirectFlags, aLoadInfoForwarder, aResponseHead, + aSecurityInfo, aChannelId, aTiming); + })); + return IPC_OK(); +} + +nsresult HttpChannelChild::SetupRedirect(nsIURI* uri, + const nsHttpResponseHead* responseHead, + const uint32_t& redirectFlags, + nsIChannel** outChannel) { + LOG(("HttpChannelChild::SetupRedirect [this=%p]\n", this)); + + if (mCanceled) { + return NS_ERROR_ABORT; + } + + nsresult rv; + nsCOMPtr<nsIIOService> ioService; + rv = gHttpHandler->GetIOService(getter_AddRefs(ioService)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIChannel> newChannel; + nsCOMPtr<nsILoadInfo> redirectLoadInfo = + CloneLoadInfoForRedirect(uri, redirectFlags); + rv = NS_NewChannelInternal(getter_AddRefs(newChannel), uri, redirectLoadInfo, + nullptr, // PerformanceStorage + nullptr, // aLoadGroup + nullptr, // aCallbacks + nsIRequest::LOAD_NORMAL, ioService); + NS_ENSURE_SUCCESS(rv, rv); + + // We won't get OnStartRequest, set cookies here. + mResponseHead = MakeUnique<nsHttpResponseHead>(*responseHead); + + bool rewriteToGET = HttpBaseChannel::ShouldRewriteRedirectToGET( + mResponseHead->Status(), mRequestHead.ParsedMethod()); + + rv = SetupReplacementChannel(uri, newChannel, !rewriteToGET, redirectFlags); + NS_ENSURE_SUCCESS(rv, rv); + + mRedirectChannelChild = do_QueryInterface(newChannel); + newChannel.forget(outChannel); + + return NS_OK; +} + +void HttpChannelChild::Redirect1Begin( + const uint32_t& registrarId, nsIURI* newOriginalURI, + const uint32_t& newLoadFlags, const uint32_t& redirectFlags, + const ParentLoadInfoForwarderArgs& loadInfoForwarder, + const nsHttpResponseHead& responseHead, + nsITransportSecurityInfo* securityInfo, const uint64_t& channelId, + const ResourceTimingStructArgs& timing) { + nsresult rv; + + LOG(("HttpChannelChild::Redirect1Begin [this=%p]\n", this)); + + MOZ_ASSERT(newOriginalURI, "newOriginalURI should not be null"); + + ipc::MergeParentLoadInfoForwarder(loadInfoForwarder, mLoadInfo); + ResourceTimingStructArgsToTimingsStruct(timing, mTransactionTimings); + + if (profiler_thread_is_being_profiled_for_markers()) { + nsAutoCString requestMethod; + GetRequestMethod(requestMethod); + nsAutoCString contentType; + responseHead.ContentType(contentType); + + profiler_add_network_marker( + mURI, requestMethod, mPriority, mChannelId, + NetworkLoadType::LOAD_REDIRECT, mLastStatusReported, TimeStamp::Now(), + 0, kCacheUnknown, mLoadInfo->GetInnerWindowID(), + mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0, + &mTransactionTimings, std::move(mSource), + Some(nsDependentCString(contentType.get())), newOriginalURI, + redirectFlags, channelId); + } + + mSecurityInfo = securityInfo; + + nsCOMPtr<nsIChannel> newChannel; + rv = SetupRedirect(newOriginalURI, &responseHead, redirectFlags, + getter_AddRefs(newChannel)); + + if (NS_SUCCEEDED(rv)) { + MOZ_ALWAYS_SUCCEEDS(newChannel->SetLoadFlags(newLoadFlags)); + + if (mRedirectChannelChild) { + // Set the channelId allocated in parent to the child instance + nsCOMPtr<nsIHttpChannel> httpChannel = + do_QueryInterface(mRedirectChannelChild); + if (httpChannel) { + rv = httpChannel->SetChannelId(channelId); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + mRedirectChannelChild->ConnectParent(registrarId); + } + + nsCOMPtr<nsISerialEventTarget> target = GetNeckoTarget(); + MOZ_ASSERT(target); + + rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags, + target); + } + + if (NS_FAILED(rv)) OnRedirectVerifyCallback(rv); +} + +mozilla::ipc::IPCResult HttpChannelChild::RecvRedirect3Complete() { + LOG(("HttpChannelChild::RecvRedirect3Complete [this=%p]\n", this)); + nsCOMPtr<nsIChannel> redirectChannel = + do_QueryInterface(mRedirectChannelChild); + MOZ_ASSERT(redirectChannel); + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, [self = UnsafePtr<HttpChannelChild>(this), redirectChannel]() { + nsresult rv = NS_OK; + Unused << self->GetStatus(&rv); + if (NS_FAILED(rv)) { + // Pre-redirect channel was canceled. Call |HandleAsyncAbort|, so + // mListener's OnStart/StopRequest can be called. Nothing else will + // trigger these notification after this point. + // We do this before |CompleteRedirectSetup|, so post-redirect channel + // stays unopened and we also make sure that OnStart/StopRequest won't + // be called twice. + self->HandleAsyncAbort(); + + nsCOMPtr<nsIHttpChannelChild> chan = + do_QueryInterface(redirectChannel); + RefPtr<HttpChannelChild> httpChannelChild = + static_cast<HttpChannelChild*>(chan.get()); + if (httpChannelChild) { + // For sending an IPC message to parent channel so that the loading + // can be cancelled. + Unused << httpChannelChild->CancelWithReason( + rv, "HttpChannelChild Redirect3 failed"_ns); + + // The post-redirect channel could still get OnStart/StopRequest IPC + // messages from parent, but the mListener is still null. So, we + // call |DoNotifyListener| to pretend that OnStart/StopRequest are + // already called. + httpChannelChild->DoNotifyListener(); + } + return; + } + + self->Redirect3Complete(); + })); + return IPC_OK(); +} + +mozilla::ipc::IPCResult HttpChannelChild::RecvRedirectFailed( + const nsresult& status) { + LOG(("HttpChannelChild::RecvRedirectFailed this=%p status=%X\n", this, + static_cast<uint32_t>(status))); + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, [self = UnsafePtr<HttpChannelChild>(this), status]() { + nsCOMPtr<nsIRedirectResultListener> vetoHook; + self->GetCallback(vetoHook); + if (vetoHook) { + vetoHook->OnRedirectResult(status); + } + + if (RefPtr<HttpChannelChild> httpChannelChild = + do_QueryObject(self->mRedirectChannelChild)) { + // For sending an IPC message to parent channel so that the loading + // can be cancelled. + Unused << httpChannelChild->CancelWithReason( + status, "HttpChannelChild RecvRedirectFailed"_ns); + + // The post-redirect channel could still get OnStart/StopRequest IPC + // messages from parent, but the mListener is still null. So, we + // call |DoNotifyListener| to pretend that OnStart/StopRequest are + // already called. + httpChannelChild->DoNotifyListener(); + } + })); + + return IPC_OK(); +} + +void HttpChannelChild::ProcessNotifyClassificationFlags( + uint32_t aClassificationFlags, bool aIsThirdParty) { + LOG( + ("HttpChannelChild::ProcessNotifyClassificationFlags thirdparty=%d " + "flags=%" PRIu32 " [this=%p]\n", + static_cast<int>(aIsThirdParty), aClassificationFlags, this)); + MOZ_ASSERT(OnSocketThread()); + + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, [self = UnsafePtr<HttpChannelChild>(this), aClassificationFlags, + aIsThirdParty]() { + self->AddClassificationFlags(aClassificationFlags, aIsThirdParty); + })); +} + +void HttpChannelChild::ProcessSetClassifierMatchedInfo( + const nsACString& aList, const nsACString& aProvider, + const nsACString& aFullHash) { + LOG(("HttpChannelChild::ProcessSetClassifierMatchedInfo [this=%p]\n", this)); + MOZ_ASSERT(OnSocketThread()); + + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, + [self = UnsafePtr<HttpChannelChild>(this), aList = nsCString(aList), + aProvider = nsCString(aProvider), aFullHash = nsCString(aFullHash)]() { + self->SetMatchedInfo(aList, aProvider, aFullHash); + })); +} + +void HttpChannelChild::ProcessSetClassifierMatchedTrackingInfo( + const nsACString& aLists, const nsACString& aFullHashes) { + LOG(("HttpChannelChild::ProcessSetClassifierMatchedTrackingInfo [this=%p]\n", + this)); + MOZ_ASSERT(OnSocketThread()); + + nsTArray<nsCString> lists, fullhashes; + for (const nsACString& token : aLists.Split(',')) { + lists.AppendElement(token); + } + for (const nsACString& token : aFullHashes.Split(',')) { + fullhashes.AppendElement(token); + } + + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, [self = UnsafePtr<HttpChannelChild>(this), + lists = CopyableTArray{std::move(lists)}, + fullhashes = CopyableTArray{std::move(fullhashes)}]() { + self->SetMatchedTrackingInfo(lists, fullhashes); + })); +} + +// Completes the redirect and cleans up the old channel. +void HttpChannelChild::Redirect3Complete() { + LOG(("HttpChannelChild::Redirect3Complete [this=%p]\n", this)); + MOZ_ASSERT(NS_IsMainThread()); + + // Using an error as the default so that when we fail to forward this redirect + // to the target channel, we make sure to notify the current listener from + // CleanupRedirectingChannel. + nsresult rv = NS_BINDING_ABORTED; + + nsCOMPtr<nsIRedirectResultListener> vetoHook; + GetCallback(vetoHook); + if (vetoHook) { + vetoHook->OnRedirectResult(NS_OK); + } + + // Chrome channel has been AsyncOpen'd. Reflect this in child. + if (mRedirectChannelChild) { + rv = mRedirectChannelChild->CompleteRedirectSetup(mListener); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mSuccesfullyRedirected = NS_SUCCEEDED(rv); +#endif + } + + CleanupRedirectingChannel(rv); +} + +void HttpChannelChild::CleanupRedirectingChannel(nsresult rv) { + // Redirecting to new channel: shut this down and init new channel + if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, NS_BINDING_ABORTED); + + if (NS_SUCCEEDED(rv)) { + mLoadInfo->AppendRedirectHistoryEntry(this, false); + } else { + NS_WARNING("CompleteRedirectSetup failed, HttpChannelChild already open?"); + } + + // Release ref to new channel. + mRedirectChannelChild = nullptr; + + NotifyOrReleaseListeners(rv); + CleanupBackgroundChannel(); +} + +//----------------------------------------------------------------------------- +// HttpChannelChild::nsIChildChannel +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelChild::ConnectParent(uint32_t registrarId) { + LOG(("HttpChannelChild::ConnectParent [this=%p, id=%" PRIu32 "]\n", this, + registrarId)); + MOZ_ASSERT(NS_IsMainThread()); + mozilla::dom::BrowserChild* browserChild = nullptr; + nsCOMPtr<nsIBrowserChild> iBrowserChild; + GetCallback(iBrowserChild); + if (iBrowserChild) { + browserChild = + static_cast<mozilla::dom::BrowserChild*>(iBrowserChild.get()); + } + + if (browserChild && !browserChild->IPCOpen()) { + return NS_ERROR_FAILURE; + } + + ContentChild* cc = static_cast<ContentChild*>(gNeckoChild->Manager()); + if (cc->IsShuttingDown()) { + return NS_ERROR_FAILURE; + } + + HttpBaseChannel::SetDocshellUserAgentOverride(); + + // This must happen before the constructor message is sent. Otherwise messages + // from the parent could arrive quickly and be delivered to the wrong event + // target. + SetEventTarget(); + + if (browserChild) { + MOZ_ASSERT(browserChild->WebNavigation()); + if (BrowsingContext* bc = browserChild->GetBrowsingContext()) { + mBrowserId = bc->BrowserId(); + } + } + + HttpChannelConnectArgs connectArgs(registrarId); + if (!gNeckoChild->SendPHttpChannelConstructor( + this, browserChild, IPC::SerializedLoadContext(this), connectArgs)) { + return NS_ERROR_FAILURE; + } + + { + MutexAutoLock lock(mBgChildMutex); + + MOZ_ASSERT(!mBgChild); + MOZ_ASSERT(!mBgInitFailCallback); + + mBgInitFailCallback = NewRunnableMethod<nsresult>( + "HttpChannelChild::OnRedirectVerifyCallback", this, + &HttpChannelChild::OnRedirectVerifyCallback, NS_ERROR_FAILURE); + + RefPtr<HttpBackgroundChannelChild> bgChild = + new HttpBackgroundChannelChild(); + + MOZ_RELEASE_ASSERT(gSocketTransportService); + + RefPtr<HttpChannelChild> self = this; + nsresult rv = gSocketTransportService->Dispatch( + NewRunnableMethod<RefPtr<HttpChannelChild>>( + "HttpBackgroundChannelChild::Init", bgChild, + &HttpBackgroundChannelChild::Init, std::move(self)), + NS_DISPATCH_NORMAL); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mBgChild = std::move(bgChild); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mEverHadBgChildAtConnectParent = true; +#endif + } + + // Should wait for CompleteRedirectSetup to set the listener. + mEventQ->Suspend(); + MOZ_ASSERT(!mSuspendForWaitCompleteRedirectSetup); + mSuspendForWaitCompleteRedirectSetup = true; + + // Connect to socket process after mEventQ is suspended. + MaybeConnectToSocketProcess(); + + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::CompleteRedirectSetup(nsIStreamListener* aListener) { + LOG(("HttpChannelChild::CompleteRedirectSetup [this=%p]\n", this)); + MOZ_ASSERT(NS_IsMainThread()); + + NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS); + NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED); + + // Resume the suspension in ConnectParent. + auto eventQueueResumeGuard = MakeScopeExit([&] { + MOZ_ASSERT(mSuspendForWaitCompleteRedirectSetup); + mEventQ->Resume(); + mSuspendForWaitCompleteRedirectSetup = false; + }); + + /* + * No need to check for cancel: we don't get here if nsHttpChannel canceled + * before AsyncOpen(); if it's canceled after that, OnStart/Stop will just + * get called with error code as usual. So just setup mListener and make the + * channel reflect AsyncOpen'ed state. + */ + + mLastStatusReported = TimeStamp::Now(); + if (profiler_thread_is_being_profiled_for_markers()) { + nsAutoCString requestMethod; + GetRequestMethod(requestMethod); + + profiler_add_network_marker( + mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START, + mChannelCreationTimestamp, mLastStatusReported, 0, kCacheUnknown, + mLoadInfo->GetInnerWindowID(), + mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0); + } + StoreIsPending(true); + StoreWasOpened(true); + mListener = aListener; + + // add ourselves to the load group. + if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr); + + // We already have an open IPDL connection to the parent. If on-modify-request + // listeners or load group observers canceled us, let the parent handle it + // and send it back to us naturally. + return NS_OK; +} + +//----------------------------------------------------------------------------- +// HttpChannelChild::nsIAsyncVerifyRedirectCallback +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelChild::OnRedirectVerifyCallback(nsresult aResult) { + LOG(("HttpChannelChild::OnRedirectVerifyCallback [this=%p]\n", this)); + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIURI> redirectURI; + + DebugOnly<nsresult> rv = NS_OK; + + nsCOMPtr<nsIHttpChannel> newHttpChannel = + do_QueryInterface(mRedirectChannelChild); + + if (NS_SUCCEEDED(aResult) && !mRedirectChannelChild) { + // mRedirectChannelChild doesn't exist means we're redirecting to a protocol + // that doesn't implement nsIChildChannel. The redirect result should be set + // as failed by veto listeners and shouldn't enter this condition. As the + // last resort, we synthesize the error result as NS_ERROR_DOM_BAD_URI here + // to let nsHttpChannel::ContinueProcessResponse2 know it's redirecting to + // another protocol and throw an error. + LOG((" redirecting to a protocol that doesn't implement nsIChildChannel")); + aResult = NS_ERROR_DOM_BAD_URI; + } + + nsCOMPtr<nsIReferrerInfo> referrerInfo; + if (newHttpChannel) { + // Must not be called until after redirect observers called. + newHttpChannel->SetOriginalURI(mOriginalURI); + referrerInfo = newHttpChannel->GetReferrerInfo(); + } + + RequestHeaderTuples emptyHeaders; + RequestHeaderTuples* headerTuples = &emptyHeaders; + nsLoadFlags loadFlags = 0; + Maybe<CorsPreflightArgs> corsPreflightArgs; + + nsCOMPtr<nsIHttpChannelChild> newHttpChannelChild = + do_QueryInterface(mRedirectChannelChild); + if (newHttpChannelChild && NS_SUCCEEDED(aResult)) { + rv = newHttpChannelChild->AddCookiesToRequest(); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + rv = newHttpChannelChild->GetClientSetRequestHeaders(&headerTuples); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + newHttpChannelChild->GetClientSetCorsPreflightParameters(corsPreflightArgs); + } + + if (NS_SUCCEEDED(aResult)) { + // Note: this is where we would notify "http-on-modify-response" observers. + // We have deliberately disabled this for child processes (see bug 806753) + // + // After we verify redirect, nsHttpChannel may hit the network: must give + // "http-on-modify-request" observers the chance to cancel before that. + // base->CallOnModifyRequestObservers(); + + nsCOMPtr<nsIHttpChannelInternal> newHttpChannelInternal = + do_QueryInterface(mRedirectChannelChild); + if (newHttpChannelInternal) { + Unused << newHttpChannelInternal->GetApiRedirectToURI( + getter_AddRefs(redirectURI)); + } + + nsCOMPtr<nsIRequest> request = do_QueryInterface(mRedirectChannelChild); + if (request) { + request->GetLoadFlags(&loadFlags); + } + } + + uint32_t sourceRequestBlockingReason = 0; + mLoadInfo->GetRequestBlockingReason(&sourceRequestBlockingReason); + + Maybe<ChildLoadInfoForwarderArgs> targetLoadInfoForwarder; + nsCOMPtr<nsIChannel> newChannel = do_QueryInterface(mRedirectChannelChild); + if (newChannel) { + ChildLoadInfoForwarderArgs args; + nsCOMPtr<nsILoadInfo> loadInfo = newChannel->LoadInfo(); + LoadInfoToChildLoadInfoForwarder(loadInfo, &args); + targetLoadInfoForwarder.emplace(args); + } + + if (CanSend()) { + SendRedirect2Verify(aResult, *headerTuples, sourceRequestBlockingReason, + targetLoadInfoForwarder, loadFlags, referrerInfo, + redirectURI, corsPreflightArgs); + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// HttpChannelChild::nsIRequest +//----------------------------------------------------------------------------- + +NS_IMETHODIMP HttpChannelChild::SetCanceledReason(const nsACString& aReason) { + return SetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP HttpChannelChild::GetCanceledReason(nsACString& aReason) { + return GetCanceledReasonImpl(aReason); +} + +NS_IMETHODIMP +HttpChannelChild::CancelWithReason(nsresult aStatus, + const nsACString& aReason) { + return CancelWithReasonImpl(aStatus, aReason); +} + +NS_IMETHODIMP +HttpChannelChild::Cancel(nsresult aStatus) { + LOG(("HttpChannelChild::Cancel [this=%p, status=%" PRIx32 "]\n", this, + static_cast<uint32_t>(aStatus))); + // only logging on parent is necessary + Maybe<nsCString> logStack = CallingScriptLocationString(); + Maybe<nsCString> logOnParent; + if (logStack.isSome()) { + logOnParent = Some(""_ns); + logOnParent->AppendPrintf( + "[this=%p] cancelled call in child process from script: %s", this, + logStack->get()); + } + + MOZ_ASSERT(NS_IsMainThread()); + + if (!mCanceled) { + // If this cancel occurs before nsHttpChannel has been set up, AsyncOpen + // is responsible for cleaning up. + mCanceled = true; + mStatus = aStatus; + + bool remoteChannelExists = RemoteChannelExists(); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mCanSendAtCancel = CanSend(); + mRemoteChannelExistedAtCancel = remoteChannelExists; +#endif + + if (remoteChannelExists) { + SendCancel(aStatus, mLoadInfo->GetRequestBlockingReason(), + mCanceledReason, logOnParent); + } else if (MOZ_UNLIKELY(!LoadOnStartRequestCalled() || + !LoadOnStopRequestCalled())) { + Unused << AsyncAbort(mStatus); + } + } + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::Suspend() { + LOG(("HttpChannelChild::Suspend [this=%p, mSuspendCount=%" PRIu32 "\n", this, + mSuspendCount + 1)); + MOZ_ASSERT(NS_IsMainThread()); + + LogCallingScriptLocation(this); + + // SendSuspend only once, when suspend goes from 0 to 1. + // Don't SendSuspend at all if we're diverting callbacks to the parent; + // suspend will be called at the correct time in the parent itself. + if (!mSuspendCount++) { + if (RemoteChannelExists()) { + SendSuspend(); + mSuspendSent = true; + } + } + mEventQ->Suspend(); + + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::Resume() { + LOG(("HttpChannelChild::Resume [this=%p, mSuspendCount=%" PRIu32 "\n", this, + mSuspendCount - 1)); + MOZ_ASSERT(NS_IsMainThread()); + NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED); + + LogCallingScriptLocation(this); + + nsresult rv = NS_OK; + + // SendResume only once, when suspend count drops to 0. + // Don't SendResume at all if we're diverting callbacks to the parent (unless + // suspend was sent earlier); otherwise, resume will be called at the correct + // time in the parent itself. + if (!--mSuspendCount) { + if (RemoteChannelExists() && mSuspendSent) { + SendResume(); + } + if (mCallOnResume) { + nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget(); + MOZ_ASSERT(neckoTarget); + + RefPtr<HttpChannelChild> self = this; + std::function<nsresult(HttpChannelChild*)> callOnResume = nullptr; + std::swap(callOnResume, mCallOnResume); + rv = neckoTarget->Dispatch( + NS_NewRunnableFunction( + "net::HttpChannelChild::mCallOnResume", + [callOnResume, self{std::move(self)}]() { callOnResume(self); }), + NS_DISPATCH_NORMAL); + } + } + mEventQ->Resume(); + + return rv; +} + +//----------------------------------------------------------------------------- +// HttpChannelChild::nsIChannel +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelChild::GetSecurityInfo(nsITransportSecurityInfo** aSecurityInfo) { + NS_ENSURE_ARG_POINTER(aSecurityInfo); + *aSecurityInfo = do_AddRef(mSecurityInfo).take(); + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::AsyncOpen(nsIStreamListener* aListener) { + LOG(("HttpChannelChild::AsyncOpen [this=%p uri=%s]\n", this, mSpec.get())); + + nsresult rv = AsyncOpenInternal(aListener); + if (NS_FAILED(rv)) { + uint32_t blockingReason = 0; + mLoadInfo->GetRequestBlockingReason(&blockingReason); + LOG( + ("HttpChannelChild::AsyncOpen failed [this=%p rv=0x%08x " + "blocking-reason=%u]\n", + this, static_cast<uint32_t>(rv), blockingReason)); + + gHttpHandler->OnFailedOpeningRequest(this); + } + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mAsyncOpenSucceeded = NS_SUCCEEDED(rv); +#endif + return rv; +} + +nsresult HttpChannelChild::AsyncOpenInternal(nsIStreamListener* aListener) { + nsresult rv; + + nsCOMPtr<nsIStreamListener> listener = aListener; + rv = nsContentSecurityManager::doContentSecurityCheck(this, listener); + if (NS_WARN_IF(NS_FAILED(rv))) { + ReleaseListeners(); + return rv; + } + + MOZ_ASSERT( + mLoadInfo->GetSecurityMode() == 0 || + mLoadInfo->GetInitialSecurityCheckDone() || + (mLoadInfo->GetSecurityMode() == + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL && + mLoadInfo->GetLoadingPrincipal() && + mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()), + "security flags in loadInfo but doContentSecurityCheck() not called"); + + LogCallingScriptLocation(this); + + if (!mLoadGroup && !mCallbacks) { + // If no one called SetLoadGroup or SetNotificationCallbacks, the private + // state has not been updated on PrivateBrowsingChannel (which we derive + // from) Hence, we have to call UpdatePrivateBrowsing() here + UpdatePrivateBrowsing(); + } + +#ifdef DEBUG + AssertPrivateBrowsingId(); +#endif + + if (mCanceled) { + ReleaseListeners(); + return mStatus; + } + + NS_ENSURE_TRUE(gNeckoChild != nullptr, NS_ERROR_FAILURE); + NS_ENSURE_ARG_POINTER(listener); + NS_ENSURE_TRUE(!LoadIsPending(), NS_ERROR_IN_PROGRESS); + NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_ALREADY_OPENED); + + if (MaybeWaitForUploadStreamNormalization(listener, nullptr)) { + return NS_OK; + } + + if (!LoadAsyncOpenTimeOverriden()) { + mAsyncOpenTime = TimeStamp::Now(); + } + + // Port checked in parent, but duplicate here so we can return with error + // immediately + rv = NS_CheckPortSafety(mURI); + if (NS_FAILED(rv)) { + ReleaseListeners(); + return rv; + } + + nsAutoCString cookie; + if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookie))) { + mUserSetCookieHeader = cookie; + } + + DebugOnly<nsresult> check = AddCookiesToRequest(); + MOZ_ASSERT(NS_SUCCEEDED(check)); + + // + // NOTE: From now on we must return NS_OK; all errors must be handled via + // OnStart/OnStopRequest + // + + // We notify "http-on-opening-request" observers in the child + // process so that devtools can capture a stack trace at the + // appropriate spot. See bug 806753 for some information about why + // other http-* notifications are disabled in child processes. + gHttpHandler->OnOpeningRequest(this); + + mLastStatusReported = TimeStamp::Now(); + if (profiler_thread_is_being_profiled_for_markers()) { + nsAutoCString requestMethod; + GetRequestMethod(requestMethod); + + profiler_add_network_marker( + mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START, + mChannelCreationTimestamp, mLastStatusReported, 0, kCacheUnknown, + mLoadInfo->GetInnerWindowID(), + mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0); + } + StoreIsPending(true); + StoreWasOpened(true); + mListener = listener; + + // add ourselves to the load group. + if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr); + + if (mCanceled) { + // We may have been canceled already, either by on-modify-request + // listeners or by load group observers; in that case, don't create IPDL + // connection. See nsHttpChannel::AsyncOpen(). + ReleaseListeners(); + return mStatus; + } + + // Set user agent override from docshell + HttpBaseChannel::SetDocshellUserAgentOverride(); + + rv = ContinueAsyncOpen(); + if (NS_FAILED(rv)) { + ReleaseListeners(); + } + return rv; +} + +// Assigns an nsISerialEventTarget to our IPDL actor so that IPC messages are +// sent to the correct DocGroup/TabGroup. +void HttpChannelChild::SetEventTarget() { + nsCOMPtr<nsILoadInfo> loadInfo = LoadInfo(); + + nsCOMPtr<nsISerialEventTarget> target = + nsContentUtils::GetEventTargetByLoadInfo(loadInfo, TaskCategory::Network); + + if (!target) { + return; + } + + { + MutexAutoLock lock(mEventTargetMutex); + mNeckoTarget = target; + } +} + +already_AddRefed<nsISerialEventTarget> HttpChannelChild::GetNeckoTarget() { + nsCOMPtr<nsISerialEventTarget> target; + { + MutexAutoLock lock(mEventTargetMutex); + target = mNeckoTarget; + } + + if (!target) { + target = GetMainThreadSerialEventTarget(); + } + return target.forget(); +} + +already_AddRefed<nsIEventTarget> HttpChannelChild::GetODATarget() { + nsCOMPtr<nsIEventTarget> target; + { + MutexAutoLock lock(mEventTargetMutex); + if (mODATarget) { + target = mODATarget; + } else { + target = mNeckoTarget; + } + } + + if (!target) { + target = GetMainThreadSerialEventTarget(); + } + return target.forget(); +} + +nsresult HttpChannelChild::ContinueAsyncOpen() { + nsresult rv; + // + // Send request to the chrome process... + // + + mozilla::dom::BrowserChild* browserChild = nullptr; + nsCOMPtr<nsIBrowserChild> iBrowserChild; + GetCallback(iBrowserChild); + if (iBrowserChild) { + browserChild = + static_cast<mozilla::dom::BrowserChild*>(iBrowserChild.get()); + } + + // This id identifies the inner window's top-level document, + // which changes on every new load or navigation. + uint64_t contentWindowId = 0; + TimeStamp navigationStartTimeStamp; + if (browserChild) { + MOZ_ASSERT(browserChild->WebNavigation()); + if (RefPtr<Document> document = browserChild->GetTopLevelDocument()) { + contentWindowId = document->InnerWindowID(); + nsDOMNavigationTiming* navigationTiming = document->GetNavigationTiming(); + if (navigationTiming) { + navigationStartTimeStamp = + navigationTiming->GetNavigationStartTimeStamp(); + } + } + if (BrowsingContext* bc = browserChild->GetBrowsingContext()) { + mBrowserId = bc->BrowserId(); + } + } + SetTopLevelContentWindowId(contentWindowId); + + HttpChannelOpenArgs openArgs; + // No access to HttpChannelOpenArgs members, but they each have a + // function with the struct name that returns a ref. + openArgs.uri() = mURI; + openArgs.original() = mOriginalURI; + openArgs.doc() = mDocumentURI; + openArgs.apiRedirectTo() = mAPIRedirectToURI; + openArgs.loadFlags() = mLoadFlags; + openArgs.requestHeaders() = mClientSetRequestHeaders; + mRequestHead.Method(openArgs.requestMethod()); + openArgs.preferredAlternativeTypes() = mPreferredCachedAltDataTypes.Clone(); + openArgs.referrerInfo() = mReferrerInfo; + + if (mUploadStream) { + MOZ_ALWAYS_TRUE(SerializeIPCStream(do_AddRef(mUploadStream), + openArgs.uploadStream(), + /* aAllowLazy */ false)); + } + + Maybe<CorsPreflightArgs> optionalCorsPreflightArgs; + GetClientSetCorsPreflightParameters(optionalCorsPreflightArgs); + + // NB: This call forces us to cache mTopWindowURI if we haven't already. + nsCOMPtr<nsIURI> uri; + GetTopWindowURI(mURI, getter_AddRefs(uri)); + + openArgs.topWindowURI() = mTopWindowURI; + + openArgs.preflightArgs() = optionalCorsPreflightArgs; + + openArgs.uploadStreamHasHeaders() = LoadUploadStreamHasHeaders(); + openArgs.priority() = mPriority; + openArgs.classOfService() = mClassOfService; + openArgs.redirectionLimit() = mRedirectionLimit; + openArgs.allowSTS() = LoadAllowSTS(); + openArgs.thirdPartyFlags() = LoadThirdPartyFlags(); + openArgs.resumeAt() = mSendResumeAt; + openArgs.startPos() = mStartPos; + openArgs.entityID() = mEntityID; + openArgs.allowSpdy() = LoadAllowSpdy(); + openArgs.allowHttp3() = LoadAllowHttp3(); + openArgs.allowAltSvc() = LoadAllowAltSvc(); + openArgs.beConservative() = LoadBeConservative(); + openArgs.bypassProxy() = BypassProxy(); + openArgs.tlsFlags() = mTlsFlags; + openArgs.initialRwin() = mInitialRwin; + + openArgs.cacheKey() = mCacheKey; + + openArgs.blockAuthPrompt() = LoadBlockAuthPrompt(); + + openArgs.allowStaleCacheContent() = LoadAllowStaleCacheContent(); + openArgs.preferCacheLoadOverBypass() = LoadPreferCacheLoadOverBypass(); + + openArgs.contentTypeHint() = mContentTypeHint; + + rv = mozilla::ipc::LoadInfoToLoadInfoArgs(mLoadInfo, &openArgs.loadInfo()); + NS_ENSURE_SUCCESS(rv, rv); + + EnsureRequestContextID(); + openArgs.requestContextID() = mRequestContextID; + + openArgs.requestMode() = mRequestMode; + openArgs.redirectMode() = mRedirectMode; + + openArgs.channelId() = mChannelId; + + openArgs.integrityMetadata() = mIntegrityMetadata; + + openArgs.contentWindowId() = contentWindowId; + openArgs.browserId() = mBrowserId; + + LOG(("HttpChannelChild::ContinueAsyncOpen this=%p gid=%" PRIu64 + " browser id=%" PRIx64, + this, mChannelId, mBrowserId)); + + if (browserChild && !browserChild->IPCOpen()) { + return NS_ERROR_FAILURE; + } + + ContentChild* cc = static_cast<ContentChild*>(gNeckoChild->Manager()); + if (cc->IsShuttingDown()) { + return NS_ERROR_FAILURE; + } + + openArgs.launchServiceWorkerStart() = mLaunchServiceWorkerStart; + openArgs.launchServiceWorkerEnd() = mLaunchServiceWorkerEnd; + openArgs.dispatchFetchEventStart() = mDispatchFetchEventStart; + openArgs.dispatchFetchEventEnd() = mDispatchFetchEventEnd; + openArgs.handleFetchEventStart() = mHandleFetchEventStart; + openArgs.handleFetchEventEnd() = mHandleFetchEventEnd; + + openArgs.forceMainDocumentChannel() = LoadForceMainDocumentChannel(); + + openArgs.navigationStartTimeStamp() = navigationStartTimeStamp; + openArgs.earlyHintPreloaderId() = mEarlyHintPreloaderId; + + openArgs.classicScriptHintCharset() = mClassicScriptHintCharset; + + RefPtr<Document> doc; + mLoadInfo->GetLoadingDocument(getter_AddRefs(doc)); + + if (doc) { + nsAutoString documentCharacterSet; + doc->GetCharacterSet(documentCharacterSet); + openArgs.documentCharacterSet() = documentCharacterSet; + } + + // This must happen before the constructor message is sent. Otherwise messages + // from the parent could arrive quickly and be delivered to the wrong event + // target. + SetEventTarget(); + + if (!gNeckoChild->SendPHttpChannelConstructor( + this, browserChild, IPC::SerializedLoadContext(this), openArgs)) { + return NS_ERROR_FAILURE; + } + + { + MutexAutoLock lock(mBgChildMutex); + + MOZ_RELEASE_ASSERT(gSocketTransportService); + + // Service worker might use the same HttpChannelChild to do async open + // twice. Need to disconnect with previous background channel before + // creating the new one, to prevent receiving further notification + // from it. + if (mBgChild) { + RefPtr<HttpBackgroundChannelChild> prevBgChild = std::move(mBgChild); + gSocketTransportService->Dispatch( + NewRunnableMethod("HttpBackgroundChannelChild::OnChannelClosed", + prevBgChild, + &HttpBackgroundChannelChild::OnChannelClosed), + NS_DISPATCH_NORMAL); + } + + MOZ_ASSERT(!mBgInitFailCallback); + + mBgInitFailCallback = NewRunnableMethod<nsresult>( + "HttpChannelChild::FailedAsyncOpen", this, + &HttpChannelChild::FailedAsyncOpen, NS_ERROR_FAILURE); + + RefPtr<HttpBackgroundChannelChild> bgChild = + new HttpBackgroundChannelChild(); + + RefPtr<HttpChannelChild> self = this; + nsresult rv = gSocketTransportService->Dispatch( + NewRunnableMethod<RefPtr<HttpChannelChild>>( + "HttpBackgroundChannelChild::Init", bgChild, + &HttpBackgroundChannelChild::Init, self), + NS_DISPATCH_NORMAL); + + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mBgChild = std::move(bgChild); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mEverHadBgChildAtAsyncOpen = true; +#endif + } + + MaybeConnectToSocketProcess(); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// HttpChannelChild::nsIHttpChannel +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelChild::SetRequestHeader(const nsACString& aHeader, + const nsACString& aValue, bool aMerge) { + LOG(("HttpChannelChild::SetRequestHeader [this=%p]\n", this)); + nsresult rv = HttpBaseChannel::SetRequestHeader(aHeader, aValue, aMerge); + if (NS_FAILED(rv)) return rv; + + RequestHeaderTuple* tuple = mClientSetRequestHeaders.AppendElement(); + if (!tuple) return NS_ERROR_OUT_OF_MEMORY; + + tuple->mHeader = aHeader; + tuple->mValue = aValue; + tuple->mMerge = aMerge; + tuple->mEmpty = false; + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::SetEmptyRequestHeader(const nsACString& aHeader) { + LOG(("HttpChannelChild::SetEmptyRequestHeader [this=%p]\n", this)); + nsresult rv = HttpBaseChannel::SetEmptyRequestHeader(aHeader); + if (NS_FAILED(rv)) return rv; + + RequestHeaderTuple* tuple = mClientSetRequestHeaders.AppendElement(); + if (!tuple) return NS_ERROR_OUT_OF_MEMORY; + + tuple->mHeader = aHeader; + tuple->mMerge = false; + tuple->mEmpty = true; + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::RedirectTo(nsIURI* newURI) { + // disabled until/unless addons run in child or something else needs this + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +HttpChannelChild::UpgradeToSecure() { + // disabled until/unless addons run in child or something else needs this + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +HttpChannelChild::GetProtocolVersion(nsACString& aProtocolVersion) { + aProtocolVersion = mProtocolVersion; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// HttpChannelChild::nsIHttpChannelInternal +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelChild::GetIsAuthChannel(bool* aIsAuthChannel) { DROP_DEAD(); } + +//----------------------------------------------------------------------------- +// HttpChannelChild::nsICacheInfoChannel +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelChild::GetCacheTokenFetchCount(uint32_t* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + MOZ_ASSERT(NS_IsMainThread()); + + if (!mCacheEntryAvailable && !mAltDataCacheEntryAvailable) { + return NS_ERROR_NOT_AVAILABLE; + } + + *_retval = mCacheFetchCount; + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::GetCacheTokenExpirationTime(uint32_t* _retval) { + NS_ENSURE_ARG_POINTER(_retval); + MOZ_ASSERT(NS_IsMainThread()); + + if (!mCacheEntryAvailable) return NS_ERROR_NOT_AVAILABLE; + + *_retval = mCacheExpirationTime; + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::IsFromCache(bool* value) { + if (!LoadIsPending()) return NS_ERROR_NOT_AVAILABLE; + + *value = mIsFromCache; + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::GetCacheEntryId(uint64_t* aCacheEntryId) { + bool fromCache = false; + if (NS_FAILED(IsFromCache(&fromCache)) || !fromCache || + !mCacheEntryAvailable) { + return NS_ERROR_NOT_AVAILABLE; + } + + *aCacheEntryId = mCacheEntryId; + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::IsRacing(bool* aIsRacing) { + if (!LoadAfterOnStartRequestBegun()) { + return NS_ERROR_NOT_AVAILABLE; + } + *aIsRacing = mIsRacing; + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::GetCacheKey(uint32_t* cacheKey) { + MOZ_ASSERT(NS_IsMainThread()); + + *cacheKey = mCacheKey; + return NS_OK; +} +NS_IMETHODIMP +HttpChannelChild::SetCacheKey(uint32_t cacheKey) { + ENSURE_CALLED_BEFORE_ASYNC_OPEN(); + + mCacheKey = cacheKey; + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::SetAllowStaleCacheContent(bool aAllowStaleCacheContent) { + StoreAllowStaleCacheContent(aAllowStaleCacheContent); + return NS_OK; +} +NS_IMETHODIMP +HttpChannelChild::GetAllowStaleCacheContent(bool* aAllowStaleCacheContent) { + NS_ENSURE_ARG(aAllowStaleCacheContent); + *aAllowStaleCacheContent = LoadAllowStaleCacheContent(); + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::SetForceValidateCacheContent( + bool aForceValidateCacheContent) { + StoreForceValidateCacheContent(aForceValidateCacheContent); + return NS_OK; +} +NS_IMETHODIMP +HttpChannelChild::GetForceValidateCacheContent( + bool* aForceValidateCacheContent) { + NS_ENSURE_ARG(aForceValidateCacheContent); + *aForceValidateCacheContent = LoadForceValidateCacheContent(); + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::SetPreferCacheLoadOverBypass( + bool aPreferCacheLoadOverBypass) { + StorePreferCacheLoadOverBypass(aPreferCacheLoadOverBypass); + return NS_OK; +} +NS_IMETHODIMP +HttpChannelChild::GetPreferCacheLoadOverBypass( + bool* aPreferCacheLoadOverBypass) { + NS_ENSURE_ARG(aPreferCacheLoadOverBypass); + *aPreferCacheLoadOverBypass = LoadPreferCacheLoadOverBypass(); + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::PreferAlternativeDataType( + const nsACString& aType, const nsACString& aContentType, + PreferredAlternativeDataDeliveryType aDeliverAltData) { + ENSURE_CALLED_BEFORE_ASYNC_OPEN(); + + mPreferredCachedAltDataTypes.AppendElement(PreferredAlternativeDataTypeParams( + nsCString(aType), nsCString(aContentType), aDeliverAltData)); + return NS_OK; +} + +const nsTArray<PreferredAlternativeDataTypeParams>& +HttpChannelChild::PreferredAlternativeDataTypes() { + return mPreferredCachedAltDataTypes; +} + +NS_IMETHODIMP +HttpChannelChild::GetAlternativeDataType(nsACString& aType) { + // Must be called during or after OnStartRequest + if (!LoadAfterOnStartRequestBegun()) { + return NS_ERROR_NOT_AVAILABLE; + } + + aType = mAvailableCachedAltDataType; + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::OpenAlternativeOutputStream(const nsACString& aType, + int64_t aPredictedSize, + nsIAsyncOutputStream** _retval) { + MOZ_ASSERT(NS_IsMainThread(), "Main thread only"); + + if (!CanSend()) { + return NS_ERROR_NOT_AVAILABLE; + } + if (static_cast<ContentChild*>(gNeckoChild->Manager())->IsShuttingDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget(); + MOZ_ASSERT(neckoTarget); + + RefPtr<AltDataOutputStreamChild> stream = new AltDataOutputStreamChild(); + stream->AddIPDLReference(); + + if (!gNeckoChild->SendPAltDataOutputStreamConstructor( + stream, nsCString(aType), aPredictedSize, WrapNotNull(this))) { + return NS_ERROR_FAILURE; + } + + stream.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::GetOriginalInputStream(nsIInputStreamReceiver* aReceiver) { + if (aReceiver == nullptr) { + return NS_ERROR_INVALID_ARG; + } + + if (!CanSend()) { + return NS_ERROR_NOT_AVAILABLE; + } + + mOriginalInputStreamReceiver = aReceiver; + Unused << SendOpenOriginalCacheInputStream(); + + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::GetAlternativeDataInputStream(nsIInputStream** aInputStream) { + NS_ENSURE_ARG_POINTER(aInputStream); + + nsCOMPtr<nsIInputStream> is = mAltDataInputStream; + is.forget(aInputStream); + + return NS_OK; +} + +mozilla::ipc::IPCResult HttpChannelChild::RecvOriginalCacheInputStreamAvailable( + const Maybe<IPCStream>& aStream) { + nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aStream); + nsCOMPtr<nsIInputStreamReceiver> receiver; + receiver.swap(mOriginalInputStreamReceiver); + if (receiver) { + receiver->OnInputStreamReady(stream); + } + + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// HttpChannelChild::nsIResumableChannel +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelChild::ResumeAt(uint64_t startPos, const nsACString& entityID) { + LOG(("HttpChannelChild::ResumeAt [this=%p]\n", this)); + ENSURE_CALLED_BEFORE_CONNECT(); + mStartPos = startPos; + mEntityID = entityID; + mSendResumeAt = true; + return NS_OK; +} + +// GetEntityID is shared in HttpBaseChannel + +//----------------------------------------------------------------------------- +// HttpChannelChild::nsISupportsPriority +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelChild::SetPriority(int32_t aPriority) { + LOG(("HttpChannelChild::SetPriority %p p=%d", this, aPriority)); + int16_t newValue = clamped<int32_t>(aPriority, INT16_MIN, INT16_MAX); + if (mPriority == newValue) return NS_OK; + mPriority = newValue; + if (RemoteChannelExists()) SendSetPriority(mPriority); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// HttpChannelChild::nsIClassOfService +//----------------------------------------------------------------------------- +NS_IMETHODIMP +HttpChannelChild::SetClassFlags(uint32_t inFlags) { + if (mClassOfService.Flags() == inFlags) { + return NS_OK; + } + + mClassOfService.SetFlags(inFlags); + + LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this, + mClassOfService.Flags(), mClassOfService.Incremental())); + + if (RemoteChannelExists()) { + SendSetClassOfService(mClassOfService); + } + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::AddClassFlags(uint32_t inFlags) { + mClassOfService.SetFlags(inFlags | mClassOfService.Flags()); + + LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this, + mClassOfService.Flags(), mClassOfService.Incremental())); + + if (RemoteChannelExists()) { + SendSetClassOfService(mClassOfService); + } + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::ClearClassFlags(uint32_t inFlags) { + mClassOfService.SetFlags(~inFlags & mClassOfService.Flags()); + + LOG(("HttpChannelChild %p ClassOfService=%lu", this, + mClassOfService.Flags())); + + if (RemoteChannelExists()) { + SendSetClassOfService(mClassOfService); + } + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::SetClassOfService(ClassOfService inCos) { + mClassOfService = inCos; + LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this, + mClassOfService.Flags(), mClassOfService.Incremental())); + if (RemoteChannelExists()) { + SendSetClassOfService(mClassOfService); + } + return NS_OK; +} +NS_IMETHODIMP +HttpChannelChild::SetIncremental(bool inIncremental) { + mClassOfService.SetIncremental(inIncremental); + LOG(("HttpChannelChild %p ClassOfService flags=%lu inc=%d", this, + mClassOfService.Flags(), mClassOfService.Incremental())); + if (RemoteChannelExists()) { + SendSetClassOfService(mClassOfService); + } + return NS_OK; +} + +//----------------------------------------------------------------------------- +// HttpChannelChild::nsIProxiedChannel +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelChild::GetProxyInfo(nsIProxyInfo** aProxyInfo) { DROP_DEAD(); } + +NS_IMETHODIMP HttpChannelChild::GetHttpProxyConnectResponseCode( + int32_t* aResponseCode) { + DROP_DEAD(); +} + +//----------------------------------------------------------------------------- +// HttpChannelChild::nsIHttpChannelChild +//----------------------------------------------------------------------------- + +NS_IMETHODIMP HttpChannelChild::AddCookiesToRequest() { + HttpBaseChannel::AddCookiesToRequest(); + return NS_OK; +} + +NS_IMETHODIMP HttpChannelChild::GetClientSetRequestHeaders( + RequestHeaderTuples** aRequestHeaders) { + *aRequestHeaders = &mClientSetRequestHeaders; + return NS_OK; +} + +void HttpChannelChild::GetClientSetCorsPreflightParameters( + Maybe<CorsPreflightArgs>& aArgs) { + if (LoadRequireCORSPreflight()) { + CorsPreflightArgs args; + args.unsafeHeaders() = mUnsafeHeaders.Clone(); + aArgs.emplace(args); + } else { + aArgs = Nothing(); + } +} + +NS_IMETHODIMP +HttpChannelChild::RemoveCorsPreflightCacheEntry( + nsIURI* aURI, nsIPrincipal* aPrincipal, + const OriginAttributes& aOriginAttributes) { + PrincipalInfo principalInfo; + MOZ_ASSERT(aURI, "aURI should not be null"); + nsresult rv = PrincipalToPrincipalInfo(aPrincipal, &principalInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + bool result = false; + // Be careful to not attempt to send a message to the parent after the + // actor has been destroyed. + if (CanSend()) { + result = SendRemoveCorsPreflightCacheEntry(aURI, principalInfo, + aOriginAttributes); + } + return result ? NS_OK : NS_ERROR_FAILURE; +} + +//----------------------------------------------------------------------------- +// HttpChannelChild::nsIMuliPartChannel +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelChild::GetBaseChannel(nsIChannel** aBaseChannel) { + if (!mMultiPartID) { + MOZ_ASSERT(false, "Not a multipart channel"); + return NS_ERROR_NOT_AVAILABLE; + } + nsCOMPtr<nsIChannel> channel = this; + channel.forget(aBaseChannel); + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::GetPartID(uint32_t* aPartID) { + if (!mMultiPartID) { + MOZ_ASSERT(false, "Not a multipart channel"); + return NS_ERROR_NOT_AVAILABLE; + } + *aPartID = *mMultiPartID; + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::GetIsFirstPart(bool* aIsFirstPart) { + if (!mMultiPartID) { + return NS_ERROR_NOT_AVAILABLE; + } + *aIsFirstPart = mIsFirstPartOfMultiPart; + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::GetIsLastPart(bool* aIsLastPart) { + if (!mMultiPartID) { + return NS_ERROR_NOT_AVAILABLE; + } + *aIsLastPart = mIsLastPartOfMultiPart; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// HttpChannelChild::nsIThreadRetargetableRequest +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelChild::RetargetDeliveryTo(nsISerialEventTarget* aNewTarget) { + LOG(("HttpChannelChild::RetargetDeliveryTo [this=%p, aNewTarget=%p]", this, + aNewTarget)); + + MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only"); + MOZ_ASSERT(aNewTarget); + + NS_ENSURE_ARG(aNewTarget); + if (aNewTarget->IsOnCurrentThread()) { + NS_WARNING("Retargeting delivery to same thread"); + mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::successMainThread; + return NS_OK; + } + + if (mMultiPartID) { + // TODO: Maybe add a new label for this? Maybe it doesn't + // matter though, since we also blocked QI, so we shouldn't + // ever get here. + mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::failListener; + return NS_ERROR_NO_INTERFACE; + } + + // Ensure that |mListener| and any subsequent listeners can be retargeted + // to another thread. + nsresult rv = NS_OK; + nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = + do_QueryInterface(mListener, &rv); + if (!retargetableListener || NS_FAILED(rv)) { + NS_WARNING("Listener is not retargetable"); + mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::failListener; + return NS_ERROR_NO_INTERFACE; + } + + rv = retargetableListener->CheckListenerChain(); + if (NS_FAILED(rv)) { + NS_WARNING("Subsequent listeners are not retargetable"); + mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::failListenerChain; + return rv; + } + + { + MutexAutoLock lock(mEventTargetMutex); + MOZ_ASSERT(!mODATarget); + mODATarget = aNewTarget; + } + + mOMTResult = LABELS_HTTP_CHILD_OMT_STATS::success; + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) { + MutexAutoLock lock(mEventTargetMutex); + + nsCOMPtr<nsISerialEventTarget> target = mODATarget; + if (!mODATarget) { + target = GetCurrentSerialEventTarget(); + } + target.forget(aEventTarget); + return NS_OK; +} + +void HttpChannelChild::TrySendDeletingChannel() { + AUTO_PROFILER_LABEL("HttpChannelChild::TrySendDeletingChannel", NETWORK); + if (!mDeletingChannelSent.compareExchange(false, true)) { + // SendDeletingChannel is already sent. + return; + } + + if (NS_IsMainThread()) { + if (NS_WARN_IF(!CanSend())) { + // IPC actor is destroyed already, do not send more messages. + return; + } + + Unused << PHttpChannelChild::SendDeletingChannel(); + return; + } + + nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget(); + MOZ_ASSERT(neckoTarget); + + DebugOnly<nsresult> rv = neckoTarget->Dispatch( + NewNonOwningRunnableMethod( + "net::HttpChannelChild::TrySendDeletingChannel", this, + &HttpChannelChild::TrySendDeletingChannel), + NS_DISPATCH_NORMAL); + MOZ_ASSERT(NS_SUCCEEDED(rv)); +} + +nsresult HttpChannelChild::AsyncCallImpl( + void (HttpChannelChild::*funcPtr)(), + nsRunnableMethod<HttpChannelChild>** retval) { + nsresult rv; + + RefPtr<nsRunnableMethod<HttpChannelChild>> event = + NewRunnableMethod("net::HttpChannelChild::AsyncCall", this, funcPtr); + nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget(); + MOZ_ASSERT(neckoTarget); + + rv = neckoTarget->Dispatch(event, NS_DISPATCH_NORMAL); + + if (NS_SUCCEEDED(rv) && retval) { + *retval = event; + } + + return rv; +} + +nsresult HttpChannelChild::SetReferrerHeader(const nsACString& aReferrer, + bool aRespectBeforeConnect) { + // Normally this would be ENSURE_CALLED_BEFORE_CONNECT, but since the + // "connect" is done in the main process, and LoadRequestObserversCalled() is + // never set in the ChannelChild, before connect basically means before + // asyncOpen. + if (aRespectBeforeConnect) { + ENSURE_CALLED_BEFORE_ASYNC_OPEN(); + } + + // remove old referrer if any + mClientSetRequestHeaders.RemoveElementsBy( + [](const auto& header) { return "Referer"_ns.Equals(header.mHeader); }); + + return HttpBaseChannel::SetReferrerHeader(aReferrer, aRespectBeforeConnect); +} + +void HttpChannelChild::CancelOnMainThread(nsresult aRv, + const nsACString& aReason) { + LOG(("HttpChannelChild::CancelOnMainThread [this=%p]", this)); + + if (NS_IsMainThread()) { + CancelWithReason(aRv, aReason); + return; + } + + mEventQ->Suspend(); + // Cancel is expected to preempt any other channel events, thus we put this + // event in the front of mEventQ to make sure nsIStreamListener not receiving + // any ODA/OnStopRequest callbacks. + nsCString reason(aReason); + mEventQ->PrependEvent(MakeUnique<NeckoTargetChannelFunctionEvent>( + this, [self = UnsafePtr<HttpChannelChild>(this), aRv, reason]() { + self->CancelWithReason(aRv, reason); + })); + mEventQ->Resume(); +} + +mozilla::ipc::IPCResult HttpChannelChild::RecvSetPriority( + const int16_t& aPriority) { + mPriority = aPriority; + return IPC_OK(); +} + +// We don't have a copyable Endpoint and NeckoTargetChannelFunctionEvent takes +// std::function<void()>. It's not possible to avoid the copy from the type of +// lambda to std::function, so does the capture list. Hence, we're forced to +// use the old-fashioned channel event inheritance. +class AttachStreamFilterEvent : public ChannelEvent { + public: + AttachStreamFilterEvent(HttpChannelChild* aChild, + already_AddRefed<nsIEventTarget> aTarget, + Endpoint<extensions::PStreamFilterParent>&& aEndpoint) + : mChild(aChild), mTarget(aTarget), mEndpoint(std::move(aEndpoint)) {} + + already_AddRefed<nsIEventTarget> GetEventTarget() override { + nsCOMPtr<nsIEventTarget> target = mTarget; + return target.forget(); + } + + void Run() override { + extensions::StreamFilterParent::Attach(mChild, std::move(mEndpoint)); + } + + private: + HttpChannelChild* mChild; + nsCOMPtr<nsIEventTarget> mTarget; + Endpoint<extensions::PStreamFilterParent> mEndpoint; +}; + +void HttpChannelChild::RegisterStreamFilter( + RefPtr<extensions::StreamFilterParent>& aStreamFilter) { + MOZ_ASSERT(NS_IsMainThread()); + mStreamFilters.AppendElement(aStreamFilter); +} + +void HttpChannelChild::ProcessAttachStreamFilter( + Endpoint<extensions::PStreamFilterParent>&& aEndpoint) { + LOG(("HttpChannelChild::ProcessAttachStreamFilter [this=%p]\n", this)); + MOZ_ASSERT(OnSocketThread()); + + mEventQ->RunOrEnqueue(new AttachStreamFilterEvent(this, GetNeckoTarget(), + std::move(aEndpoint))); +} + +void HttpChannelChild::OnDetachStreamFilters() { + LOG(("HttpChannelChild::OnDetachStreamFilters [this=%p]\n", this)); + MOZ_ASSERT(NS_IsMainThread()); + for (auto& StreamFilter : mStreamFilters) { + StreamFilter->Disconnect("ServiceWorker fallback redirection"_ns); + } + mStreamFilters.Clear(); +} + +void HttpChannelChild::ProcessDetachStreamFilters() { + LOG(("HttpChannelChild::ProcessDetachStreamFilter [this=%p]\n", this)); + MOZ_ASSERT(OnSocketThread()); + + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, [self = UnsafePtr<HttpChannelChild>(this)]() { + self->OnDetachStreamFilters(); + })); +} + +void HttpChannelChild::ActorDestroy(ActorDestroyReason aWhy) { + MOZ_ASSERT(NS_IsMainThread()); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mActorDestroyReason.emplace(aWhy); +#endif + + // OnStartRequest might be dropped if IPDL is destroyed abnormally + // and BackgroundChild might have pending IPC messages. + // Clean up BackgroundChild at this time to prevent memleak. + if (aWhy != Deletion) { + // Make sure all the messages are processed. + AutoEventEnqueuer ensureSerialDispatch(mEventQ); + + mStatus = NS_ERROR_DOCSHELL_DYING; + HandleAsyncAbort(); + + // Cleanup the background channel before we resume the eventQ so we don't + // get any other events. + CleanupBackgroundChannel(); + + mIPCActorDeleted = true; + mCanceled = true; + } +} + +mozilla::ipc::IPCResult HttpChannelChild::RecvLogBlockedCORSRequest( + const nsAString& aMessage, const nsACString& aCategory, + const bool& aIsWarning) { + Unused << LogBlockedCORSRequest(aMessage, aCategory, aIsWarning); + return IPC_OK(); +} + +NS_IMETHODIMP +HttpChannelChild::LogBlockedCORSRequest(const nsAString& aMessage, + const nsACString& aCategory, + bool aIsWarning) { + uint64_t innerWindowID = mLoadInfo->GetInnerWindowID(); + bool privateBrowsing = !!mLoadInfo->GetOriginAttributes().mPrivateBrowsingId; + bool fromChromeContext = + mLoadInfo->TriggeringPrincipal()->IsSystemPrincipal(); + nsCORSListenerProxy::LogBlockedCORSRequest(innerWindowID, privateBrowsing, + fromChromeContext, aMessage, + aCategory, aIsWarning); + return NS_OK; +} + +mozilla::ipc::IPCResult HttpChannelChild::RecvLogMimeTypeMismatch( + const nsACString& aMessageName, const bool& aWarning, const nsAString& aURL, + const nsAString& aContentType) { + Unused << LogMimeTypeMismatch(aMessageName, aWarning, aURL, aContentType); + return IPC_OK(); +} + +NS_IMETHODIMP +HttpChannelChild::LogMimeTypeMismatch(const nsACString& aMessageName, + bool aWarning, const nsAString& aURL, + const nsAString& aContentType) { + RefPtr<Document> doc; + mLoadInfo->GetLoadingDocument(getter_AddRefs(doc)); + + AutoTArray<nsString, 2> params; + params.AppendElement(aURL); + params.AppendElement(aContentType); + nsContentUtils::ReportToConsole( + aWarning ? nsIScriptError::warningFlag : nsIScriptError::errorFlag, + "MIMEMISMATCH"_ns, doc, nsContentUtils::eSECURITY_PROPERTIES, + nsCString(aMessageName).get(), params); + return NS_OK; +} + +nsresult HttpChannelChild::MaybeLogCOEPError(nsresult aStatus) { + if (aStatus == NS_ERROR_DOM_CORP_FAILED) { + RefPtr<Document> doc; + mLoadInfo->GetLoadingDocument(getter_AddRefs(doc)); + + nsAutoCString url; + mURI->GetSpec(url); + + AutoTArray<nsString, 2> params; + params.AppendElement(NS_ConvertUTF8toUTF16(url)); + // The MDN URL intentionally ends with a # so the webconsole linkification + // doesn't ignore the final ) of the URL + params.AppendElement( + u"https://developer.mozilla.org/docs/Web/HTTP/Cross-Origin_Resource_Policy_(CORP)#"_ns); + nsContentUtils::ReportToConsole(nsIScriptError::errorFlag, "COEP"_ns, doc, + nsContentUtils::eNECKO_PROPERTIES, + "CORPBlocked", params); + } + + return NS_OK; +} + +nsresult HttpChannelChild::CrossProcessRedirectFinished(nsresult aStatus) { + if (!CanSend()) { + return NS_BINDING_FAILED; + } + + if (!mCanceled && NS_SUCCEEDED(mStatus)) { + mStatus = aStatus; + } + + return mStatus; +} + +void HttpChannelChild::DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() { +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mDoDiagnosticAssertWhenOnStopNotCalledOnDestroy = true; +#endif +} + +void HttpChannelChild::MaybeConnectToSocketProcess() { + if (!nsIOService::UseSocketProcess()) { + return; + } + + if (!StaticPrefs::network_send_ODA_to_content_directly()) { + return; + } + + RefPtr<HttpBackgroundChannelChild> bgChild; + { + MutexAutoLock lock(mBgChildMutex); + bgChild = mBgChild; + } + SocketProcessBridgeChild::GetSocketProcessBridge()->Then( + GetCurrentSerialEventTarget(), __func__, + [bgChild]() { + gSocketTransportService->Dispatch( + NewRunnableMethod("HttpBackgroundChannelChild::CreateDataBridge", + bgChild, + &HttpBackgroundChannelChild::CreateDataBridge), + NS_DISPATCH_NORMAL); + }, + []() { NS_WARNING("Failed to create SocketProcessBridgeChild"); }); +} + +NS_IMETHODIMP +HttpChannelChild::SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) { + return NS_OK; +} + +NS_IMETHODIMP HttpChannelChild::SetWebTransportSessionEventListener( + WebTransportSessionEventListener* aListener) { + return NS_OK; +} + +} // namespace mozilla::net |