Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// HttpLog.h should generally be included first
#include "HttpLog.h"
#include "mozilla/ConsoleReportCollector.h"
#include "mozilla/ipc/FileDescriptorSetParent.h"
#include "mozilla/ipc/IPCStreamUtils.h"
#include "mozilla/net/HttpChannelParent.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/ContentProcessManager.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/ServiceWorkerUtils.h"
#include "mozilla/dom/BrowserParent.h"
#include "mozilla/net/NeckoParent.h"
#include "mozilla/InputStreamLengthHelper.h"
#include "mozilla/IntegerPrintfMacros.h"
#include "mozilla/StoragePrincipalHelper.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "HttpBackgroundChannelParent.h"
#include "ParentChannelListener.h"
#include "nsHttpHandler.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsISupportsPriority.h"
#include "nsIAuthPromptProvider.h"
#include "mozilla/net/BackgroundChannelRegistrar.h"
#include "nsSerializationHelper.h"
#include "nsISerializable.h"
#include "nsIApplicationCacheService.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "mozilla/ipc/URIUtils.h"
#include "SerializedLoadContext.h"
#include "nsIAuthPrompt.h"
#include "nsIAuthPrompt2.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/LoadInfo.h"
#include "nsQueryObject.h"
#include "mozilla/BasePrincipal.h"
#include "nsCORSListenerProxy.h"
#include "nsIIPCSerializableInputStream.h"
#include "nsIPrompt.h"
#include "nsIPromptFactory.h"
#include "mozilla/net/RedirectChannelRegistrar.h"
#include "nsIWindowWatcher.h"
#include "mozilla/dom/Document.h"
#include "nsISecureBrowserUI.h"
#include "nsStreamUtils.h"
#include "nsStringStream.h"
#include "nsThreadUtils.h"
#include "nsQueryObject.h"
#include "nsIMultiPartChannel.h"
using mozilla::BasePrincipal;
using namespace mozilla::dom;
using namespace mozilla::ipc;
namespace mozilla {
namespace net {
HttpChannelParent::HttpChannelParent(dom::BrowserParent* iframeEmbedding,
nsILoadContext* aLoadContext,
PBOverrideStatus aOverrideStatus)
: mLoadContext(aLoadContext),
mIPCClosed(false),
mPBOverride(aOverrideStatus),
mStatus(NS_OK),
mIgnoreProgress(false),
mSentRedirect1BeginFailed(false),
mReceivedRedirect2Verify(false),
mHasSuspendedByBackPressure(false),
mPendingDiversion(false),
mDivertingFromChild(false),
mDivertedOnStartRequest(false),
mSuspendedForDiversion(false),
mSuspendAfterSynthesizeResponse(false),
mWillSynthesizeResponse(false),
mCacheNeedFlowControlInitialized(false),
mNeedFlowControl(true),
mSuspendedForFlowControl(false),
mAfterOnStartRequestBegun(false),
mDataSentToChildProcess(false) {
LOG(("Creating HttpChannelParent [this=%p]\n", this));
// Ensure gHttpHandler is initialized: we need the atom table up and running.
nsCOMPtr<nsIHttpProtocolHandler> dummyInitializer =
do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "http");
MOZ_ASSERT(gHttpHandler);
mHttpHandler = gHttpHandler;
mBrowserParent = iframeEmbedding;
mSendWindowSize = gHttpHandler->SendWindowSize();
mEventQ =
new ChannelEventQueue(static_cast<nsIParentRedirectingChannel*>(this));
}
HttpChannelParent::~HttpChannelParent() {
LOG(("Destroying HttpChannelParent [this=%p]\n", this));
CleanupBackgroundChannel();
MOZ_ASSERT(!mRedirectCallback);
if (NS_WARN_IF(mRedirectCallback)) {
mRedirectCallback->OnRedirectVerifyCallback(NS_ERROR_UNEXPECTED);
mRedirectCallback = nullptr;
}
}
void HttpChannelParent::ActorDestroy(ActorDestroyReason why) {
// We may still have refcount>0 if nsHttpChannel hasn't called OnStopRequest
// yet, but child process has crashed. We must not try to send any more msgs
// to child, or IPDL will kill chrome process, too.
mIPCClosed = true;
// If this is an intercepted channel, we need to make sure that any resources
// are cleaned up to avoid leaks.
if (mParentListener) {
mParentListener->ClearInterceptedChannel(this);
}
CleanupBackgroundChannel();
}
bool HttpChannelParent::Init(const HttpChannelCreationArgs& aArgs) {
LOG(("HttpChannelParent::Init [this=%p]\n", this));
AUTO_PROFILER_LABEL("HttpChannelParent::Init", NETWORK);
switch (aArgs.type()) {
case HttpChannelCreationArgs::THttpChannelOpenArgs: {
const HttpChannelOpenArgs& a = aArgs.get_HttpChannelOpenArgs();
return DoAsyncOpen(
a.uri(), a.original(), a.doc(), a.referrerInfo(), a.apiRedirectTo(),
a.topWindowURI(), a.loadFlags(), a.requestHeaders(),
a.requestMethod(), a.uploadStream(), a.uploadStreamHasHeaders(),
a.priority(), a.classOfService(), a.redirectionLimit(), a.allowSTS(),
a.thirdPartyFlags(), a.resumeAt(), a.startPos(), a.entityID(),
a.chooseApplicationCache(), a.appCacheClientID(), a.allowSpdy(),
a.allowAltSvc(), a.beConservative(), a.tlsFlags(), a.loadInfo(),
a.synthesizedResponseHead(), a.synthesizedSecurityInfoSerialization(),
a.cacheKey(), a.requestContextID(), a.preflightArgs(),
a.initialRwin(), a.blockAuthPrompt(),
a.suspendAfterSynthesizeResponse(), a.allowStaleCacheContent(),
a.preferCacheLoadOverBypass(), a.contentTypeHint(), a.corsMode(),
a.redirectMode(), a.channelId(), a.integrityMetadata(),
a.contentWindowId(), a.preferredAlternativeTypes(),
a.topLevelOuterContentWindowId(), a.launchServiceWorkerStart(),
a.launchServiceWorkerEnd(), a.dispatchFetchEventStart(),
a.dispatchFetchEventEnd(), a.handleFetchEventStart(),
a.handleFetchEventEnd(), a.forceMainDocumentChannel(),
a.navigationStartTimeStamp(), a.hasNonEmptySandboxingFlag());
}
case HttpChannelCreationArgs::THttpChannelConnectArgs: {
const HttpChannelConnectArgs& cArgs = aArgs.get_HttpChannelConnectArgs();
return ConnectChannel(cArgs.registrarId(), cArgs.shouldIntercept());
}
default:
MOZ_ASSERT_UNREACHABLE("unknown open type");
return false;
}
}
void HttpChannelParent::TryInvokeAsyncOpen(nsresult aRv) {
LOG(("HttpChannelParent::TryInvokeAsyncOpen [this=%p barrier=%u rv=%" PRIx32
"]\n",
this, mAsyncOpenBarrier, static_cast<uint32_t>(aRv)));
MOZ_ASSERT(NS_IsMainThread());
AUTO_PROFILER_LABEL("HttpChannelParent::TryInvokeAsyncOpen", NETWORK);
// TryInvokeAsyncOpen is called more than we expected.
// Assert in nightly build but ignore it in release channel.
MOZ_DIAGNOSTIC_ASSERT(mAsyncOpenBarrier > 0);
if (NS_WARN_IF(!mAsyncOpenBarrier)) {
return;
}
if (--mAsyncOpenBarrier > 0 && NS_SUCCEEDED(aRv)) {
// Need to wait for more events.
return;
}
InvokeAsyncOpen(aRv);
}
void HttpChannelParent::OnBackgroundParentReady(
HttpBackgroundChannelParent* aBgParent) {
LOG(("HttpChannelParent::OnBackgroundParentReady [this=%p bgParent=%p]\n",
this, aBgParent));
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!mBgParent);
mBgParent = aBgParent;
mPromise.ResolveIfExists(true, __func__);
}
void HttpChannelParent::OnBackgroundParentDestroyed() {
LOG(("HttpChannelParent::OnBackgroundParentDestroyed [this=%p]\n", this));
MOZ_ASSERT(NS_IsMainThread());
if (!mPromise.IsEmpty()) {
MOZ_ASSERT(!mBgParent);
mPromise.Reject(NS_ERROR_FAILURE, __func__);
return;
}
if (!mBgParent) {
return;
}
// Background channel is closed unexpectly, abort PHttpChannel operation.
mBgParent = nullptr;
Delete();
}
void HttpChannelParent::CleanupBackgroundChannel() {
LOG(("HttpChannelParent::CleanupBackgroundChannel [this=%p bgParent=%p]\n",
this, mBgParent.get()));
MOZ_ASSERT(NS_IsMainThread());
if (mBgParent) {
RefPtr<HttpBackgroundChannelParent> bgParent = std::move(mBgParent);
bgParent->OnChannelClosed();
return;
}
// The nsHttpChannel may have a reference to this parent, release it
// to avoid circular references.
RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
if (httpChannelImpl) {
httpChannelImpl->SetWarningReporter(nullptr);
}
if (!mPromise.IsEmpty()) {
mRequest.DisconnectIfExists();
mPromise.Reject(NS_ERROR_FAILURE, __func__);
if (!mChannel) {
return;
}
// This HttpChannelParent might still have a reference from
// BackgroundChannelRegistrar.
nsCOMPtr<nsIBackgroundChannelRegistrar> registrar =
BackgroundChannelRegistrar::GetOrCreate();
MOZ_ASSERT(registrar);
registrar->DeleteChannel(mChannel->ChannelId());
// If mAsyncOpenBarrier is greater than zero, it means AsyncOpen procedure
// is still on going. we need to abort AsyncOpen with failure to destroy
// PHttpChannel actor.
if (mAsyncOpenBarrier) {
TryInvokeAsyncOpen(NS_ERROR_FAILURE);
}
}
}
base::ProcessId HttpChannelParent::OtherPid() const {
if (mIPCClosed) {
return 0;
}
return IProtocol::OtherPid();
}
//-----------------------------------------------------------------------------
// HttpChannelParent::nsISupports
//-----------------------------------------------------------------------------
NS_IMPL_ADDREF(HttpChannelParent)
NS_IMPL_RELEASE(HttpChannelParent)
NS_INTERFACE_MAP_BEGIN(HttpChannelParent)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
NS_INTERFACE_MAP_ENTRY(nsIParentChannel)
NS_INTERFACE_MAP_ENTRY(nsIAuthPromptProvider)
NS_INTERFACE_MAP_ENTRY(nsIParentRedirectingChannel)
NS_INTERFACE_MAP_ENTRY(nsIDeprecationWarner)
NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectReadyCallback)
NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
NS_INTERFACE_MAP_ENTRY(nsIRedirectResultListener)
NS_INTERFACE_MAP_ENTRY(nsIMultiPartChannelListener)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIParentRedirectingChannel)
NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpChannelParent)
NS_INTERFACE_MAP_END
//-----------------------------------------------------------------------------
// HttpChannelParent::nsIInterfaceRequestor
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelParent::GetInterface(const nsIID& aIID, void** result) {
// Only support nsIAuthPromptProvider in Content process
if (XRE_IsParentProcess() && aIID.Equals(NS_GET_IID(nsIAuthPromptProvider))) {
*result = nullptr;
return NS_OK;
}
// A system XHR can be created without reference to a window, hence mTabParent
// may be null. In that case we want to let the window watcher pick a prompt
// directly.
if (!mBrowserParent && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
aIID.Equals(NS_GET_IID(nsIAuthPrompt2)))) {
nsresult rv;
nsCOMPtr<nsIWindowWatcher> wwatch =
do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, NS_ERROR_NO_INTERFACE);
bool hasWindowCreator = false;
Unused << wwatch->HasWindowCreator(&hasWindowCreator);
if (!hasWindowCreator) {
return NS_ERROR_NO_INTERFACE;
}
nsCOMPtr<nsIPromptFactory> factory = do_QueryInterface(wwatch);
if (!factory) {
return NS_ERROR_NO_INTERFACE;
}
rv = factory->GetPrompt(nullptr, aIID, reinterpret_cast<void**>(result));
if (NS_FAILED(rv)) {
return NS_ERROR_NO_INTERFACE;
}
return NS_OK;
}
// Only support nsILoadContext if child channel's callbacks did too
if (aIID.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) {
nsCOMPtr<nsILoadContext> copy = mLoadContext;
copy.forget(result);
return NS_OK;
}
return QueryInterface(aIID, result);
}
//-----------------------------------------------------------------------------
// HttpChannelParent::PHttpChannelParent
//-----------------------------------------------------------------------------
void HttpChannelParent::AsyncOpenFailed(nsresult aRv) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(NS_FAILED(aRv));
// Break the reference cycle among HttpChannelParent,
// ParentChannelListener, and nsHttpChannel to avoid memory leakage.
mChannel = nullptr;
mParentListener = nullptr;
if (!mIPCClosed) {
Unused << SendFailedAsyncOpen(aRv);
}
}
void HttpChannelParent::InvokeAsyncOpen(nsresult rv) {
LOG(("HttpChannelParent::InvokeAsyncOpen [this=%p rv=%" PRIx32 "]\n", this,
static_cast<uint32_t>(rv)));
MOZ_ASSERT(NS_IsMainThread());
if (NS_FAILED(rv)) {
AsyncOpenFailed(rv);
return;
}
rv = mChannel->AsyncOpen(mParentListener);
if (NS_FAILED(rv)) {
AsyncOpenFailed(rv);
}
}
bool HttpChannelParent::DoAsyncOpen(
const URIParams& aURI, const Maybe<URIParams>& aOriginalURI,
const Maybe<URIParams>& aDocURI, nsIReferrerInfo* aReferrerInfo,
const Maybe<URIParams>& aAPIRedirectToURI,
const Maybe<URIParams>& aTopWindowURI, const uint32_t& aLoadFlags,
const RequestHeaderTuples& requestHeaders, const nsCString& requestMethod,
const Maybe<IPCStream>& uploadStream, const bool& uploadStreamHasHeaders,
const int16_t& priority, const uint32_t& classOfService,
const uint8_t& redirectionLimit, const bool& allowSTS,
const uint32_t& thirdPartyFlags, const bool& doResumeAt,
const uint64_t& startPos, const nsCString& entityID,
const bool& chooseApplicationCache, const nsCString& appCacheClientID,
const bool& allowSpdy, const bool& allowAltSvc, const bool& beConservative,
const uint32_t& tlsFlags, const Maybe<LoadInfoArgs>& aLoadInfoArgs,
const Maybe<nsHttpResponseHead>& aSynthesizedResponseHead,
const nsCString& aSecurityInfoSerialization, const uint32_t& aCacheKey,
const uint64_t& aRequestContextID,
const Maybe<CorsPreflightArgs>& aCorsPreflightArgs,
const uint32_t& aInitialRwin, const bool& aBlockAuthPrompt,
const bool& aSuspendAfterSynthesizeResponse,
const bool& aAllowStaleCacheContent, const bool& aPreferCacheLoadOverBypass,
const nsCString& aContentTypeHint, const uint32_t& aCorsMode,
const uint32_t& aRedirectMode, const uint64_t& aChannelId,
const nsString& aIntegrityMetadata, const uint64_t& aContentWindowId,
const nsTArray<PreferredAlternativeDataTypeParams>&
aPreferredAlternativeTypes,
const uint64_t& aTopLevelOuterContentWindowId,
const TimeStamp& aLaunchServiceWorkerStart,
const TimeStamp& aLaunchServiceWorkerEnd,
const TimeStamp& aDispatchFetchEventStart,
const TimeStamp& aDispatchFetchEventEnd,
const TimeStamp& aHandleFetchEventStart,
const TimeStamp& aHandleFetchEventEnd,
const bool& aForceMainDocumentChannel,
const TimeStamp& aNavigationStartTimeStamp,
const bool& aHasNonEmptySandboxingFlag) {
nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
if (!uri) {
// URIParams does MOZ_ASSERT if null, but we need to protect opt builds from
// null deref here.
return false;
}
nsCOMPtr<nsIURI> originalUri = DeserializeURI(aOriginalURI);
nsCOMPtr<nsIURI> docUri = DeserializeURI(aDocURI);
nsCOMPtr<nsIURI> apiRedirectToUri = DeserializeURI(aAPIRedirectToURI);
nsCOMPtr<nsIURI> topWindowUri = DeserializeURI(aTopWindowURI);
LOG(("HttpChannelParent RecvAsyncOpen [this=%p uri=%s, gid=%" PRIu64
" topwinid=%" PRIx64 "]\n",
this, uri->GetSpecOrDefault().get(), aChannelId,
aTopLevelOuterContentWindowId));
nsresult rv;
nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
if (NS_FAILED(rv)) return SendFailedAsyncOpen(rv);
nsCOMPtr<nsILoadInfo> loadInfo;
rv = mozilla::ipc::LoadInfoArgsToLoadInfo(aLoadInfoArgs,
getter_AddRefs(loadInfo));
if (NS_FAILED(rv)) {
return SendFailedAsyncOpen(rv);
}
nsCOMPtr<nsIChannel> channel;
rv = NS_NewChannelInternal(getter_AddRefs(channel), uri, loadInfo, nullptr,
nullptr, nullptr, aLoadFlags, ios);
if (NS_FAILED(rv)) {
return SendFailedAsyncOpen(rv);
}
RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(channel, &rv);
if (NS_FAILED(rv)) {
return SendFailedAsyncOpen(rv);
}
// Set attributes needed to create a FetchEvent from this channel.
httpChannel->SetCorsMode(aCorsMode);
httpChannel->SetRedirectMode(aRedirectMode);
// Set the channelId allocated in child to the parent instance
httpChannel->SetChannelId(aChannelId);
httpChannel->SetTopLevelContentWindowId(aContentWindowId);
httpChannel->SetTopLevelOuterContentWindowId(aTopLevelOuterContentWindowId);
httpChannel->SetIntegrityMetadata(aIntegrityMetadata);
RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(httpChannel);
if (httpChannelImpl) {
httpChannelImpl->SetWarningReporter(this);
}
httpChannel->SetTimingEnabled(true);
if (mPBOverride != kPBOverride_Unset) {
httpChannel->SetPrivate(mPBOverride == kPBOverride_Private ? true : false);
}
if (doResumeAt) httpChannel->ResumeAt(startPos, entityID);
if (originalUri) httpChannel->SetOriginalURI(originalUri);
if (docUri) httpChannel->SetDocumentURI(docUri);
if (aReferrerInfo) {
// Referrer header is computed in child no need to recompute here
rv =
httpChannel->SetReferrerInfoInternal(aReferrerInfo, false, false, true);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
if (apiRedirectToUri) httpChannel->RedirectTo(apiRedirectToUri);
if (topWindowUri) {
httpChannel->SetTopWindowURI(topWindowUri);
}
if (aLoadFlags != nsIRequest::LOAD_NORMAL)
httpChannel->SetLoadFlags(aLoadFlags);
if (aForceMainDocumentChannel) {
httpChannel->SetIsMainDocumentChannel(true);
}
if (aHasNonEmptySandboxingFlag) {
httpChannel->SetHasNonEmptySandboxingFlag(true);
}
for (uint32_t i = 0; i < requestHeaders.Length(); i++) {
if (requestHeaders[i].mEmpty) {
httpChannel->SetEmptyRequestHeader(requestHeaders[i].mHeader);
} else {
httpChannel->SetRequestHeader(requestHeaders[i].mHeader,
requestHeaders[i].mValue,
requestHeaders[i].mMerge);
}
}
RefPtr<ParentChannelListener> parentListener = new ParentChannelListener(
this, mBrowserParent ? mBrowserParent->GetBrowsingContext() : nullptr,
mLoadContext && mLoadContext->UsePrivateBrowsing());
httpChannel->SetRequestMethod(nsDependentCString(requestMethod.get()));
if (aCorsPreflightArgs.isSome()) {
const CorsPreflightArgs& args = aCorsPreflightArgs.ref();
httpChannel->SetCorsPreflightParameters(args.unsafeHeaders(), false);
}
nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(uploadStream);
if (stream) {
int64_t length;
if (InputStreamLengthHelper::GetSyncLength(stream, &length)) {
httpChannel->InternalSetUploadStreamLength(length >= 0 ? length : 0);
} else {
// Wait for the nputStreamLengthHelper::GetAsyncLength callback.
++mAsyncOpenBarrier;
// Let's resolve the size of the stream. The following operation is always
// async.
RefPtr<HttpChannelParent> self = this;
InputStreamLengthHelper::GetAsyncLength(stream, [self, httpChannel](
int64_t aLength) {
httpChannel->InternalSetUploadStreamLength(aLength >= 0 ? aLength : 0);
self->TryInvokeAsyncOpen(NS_OK);
});
}
httpChannel->InternalSetUploadStream(stream);
httpChannel->SetUploadStreamHasHeaders(uploadStreamHasHeaders);
}
if (aSynthesizedResponseHead.isSome()) {
parentListener->SetupInterception(aSynthesizedResponseHead.ref());
mWillSynthesizeResponse = true;
httpChannelImpl->SetCouldBeSynthesized();
if (!aSecurityInfoSerialization.IsEmpty()) {
nsCOMPtr<nsISupports> secInfo;
rv = NS_DeserializeObject(aSecurityInfoSerialization,
getter_AddRefs(secInfo));
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv),
"Deserializing security info should not fail");
rv = httpChannel->OverrideSecurityInfo(secInfo);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
nsCOMPtr<nsICacheInfoChannel> cacheChannel =
do_QueryInterface(static_cast<nsIChannel*>(httpChannel.get()));
if (cacheChannel) {
cacheChannel->SetCacheKey(aCacheKey);
for (auto& data : aPreferredAlternativeTypes) {
cacheChannel->PreferAlternativeDataType(data.type(), data.contentType(),
data.deliverAltData());
}
cacheChannel->SetAllowStaleCacheContent(aAllowStaleCacheContent);
cacheChannel->SetPreferCacheLoadOverBypass(aPreferCacheLoadOverBypass);
// This is to mark that the results are going to the content process.
if (httpChannelImpl) {
httpChannelImpl->SetAltDataForChild(true);
}
}
httpChannel->SetContentType(aContentTypeHint);
if (priority != nsISupportsPriority::PRIORITY_NORMAL) {
httpChannel->SetPriority(priority);
}
if (classOfService) {
httpChannel->SetClassFlags(classOfService);
}
httpChannel->SetRedirectionLimit(redirectionLimit);
httpChannel->SetAllowSTS(allowSTS);
httpChannel->SetThirdPartyFlags(thirdPartyFlags);
httpChannel->SetAllowSpdy(allowSpdy);
httpChannel->SetAllowAltSvc(allowAltSvc);
httpChannel->SetBeConservative(beConservative);
httpChannel->SetTlsFlags(tlsFlags);
httpChannel->SetInitialRwin(aInitialRwin);
httpChannel->SetBlockAuthPrompt(aBlockAuthPrompt);
httpChannel->SetLaunchServiceWorkerStart(aLaunchServiceWorkerStart);
httpChannel->SetLaunchServiceWorkerEnd(aLaunchServiceWorkerEnd);
httpChannel->SetDispatchFetchEventStart(aDispatchFetchEventStart);
httpChannel->SetDispatchFetchEventEnd(aDispatchFetchEventEnd);
httpChannel->SetHandleFetchEventStart(aHandleFetchEventStart);
httpChannel->SetHandleFetchEventEnd(aHandleFetchEventEnd);
httpChannel->SetNavigationStartTimeStamp(aNavigationStartTimeStamp);
nsCOMPtr<nsIApplicationCacheChannel> appCacheChan =
do_QueryObject(httpChannel);
nsCOMPtr<nsIApplicationCacheService> appCacheService =
do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
bool setChooseApplicationCache = chooseApplicationCache;
if (appCacheChan && appCacheService) {
// We might potentially want to drop this flag (that is TRUE by default)
// after we successfully associate the channel with an application cache
// reported by the channel child. Dropping it here may be too early.
appCacheChan->SetInheritApplicationCache(false);
if (!appCacheClientID.IsEmpty()) {
nsCOMPtr<nsIApplicationCache> appCache;
rv = appCacheService->GetApplicationCache(appCacheClientID,
getter_AddRefs(appCache));
if (NS_SUCCEEDED(rv)) {
appCacheChan->SetApplicationCache(appCache);
setChooseApplicationCache = false;
}
}
if (setChooseApplicationCache) {
OriginAttributes attrs;
StoragePrincipalHelper::GetOriginAttributes(
httpChannel, attrs, StoragePrincipalHelper::eRegularPrincipal);
nsCOMPtr<nsIPrincipal> principal =
BasePrincipal::CreateContentPrincipal(uri, attrs);
bool chooseAppCache = false;
// This works because we've already called SetNotificationCallbacks and
// done mPBOverride logic by this point.
chooseAppCache = NS_ShouldCheckAppCache(principal);
appCacheChan->SetChooseApplicationCache(chooseAppCache);
}
}
httpChannel->SetRequestContextID(aRequestContextID);
// Store the strong reference of channel and parent listener object until
// all the initialization procedure is complete without failure, to remove
// cycle reference in fail case and to avoid memory leakage.
mChannel = std::move(httpChannel);
mParentListener = std::move(parentListener);
mChannel->SetNotificationCallbacks(mParentListener);
mSuspendAfterSynthesizeResponse = aSuspendAfterSynthesizeResponse;
MOZ_ASSERT(!mBgParent);
MOZ_ASSERT(mPromise.IsEmpty());
// Wait for HttpBackgrounChannel to continue the async open procedure.
++mAsyncOpenBarrier;
RefPtr<HttpChannelParent> self = this;
WaitForBgParent()
->Then(
GetMainThreadSerialEventTarget(), __func__,
[self]() {
self->mRequest.Complete();
self->TryInvokeAsyncOpen(NS_OK);
},
[self](nsresult aStatus) {
self->mRequest.Complete();
self->TryInvokeAsyncOpen(aStatus);
})
->Track(mRequest);
// The stream, received from the child process, must be cloneable and seekable
// in order to allow devtools to inspect its content.
nsCOMPtr<nsIRunnable> r =
NS_NewRunnableFunction("HttpChannelParent::EnsureUploadStreamIsCloneable",
[self]() { self->TryInvokeAsyncOpen(NS_OK); });
++mAsyncOpenBarrier;
mChannel->EnsureUploadStreamIsCloneable(r);
return true;
}
RefPtr<GenericNonExclusivePromise> HttpChannelParent::WaitForBgParent() {
LOG(("HttpChannelParent::WaitForBgParent [this=%p]\n", this));
MOZ_ASSERT(!mBgParent);
if (!mChannel) {
return GenericNonExclusivePromise::CreateAndReject(NS_ERROR_FAILURE,
__func__);
}
nsCOMPtr<nsIBackgroundChannelRegistrar> registrar =
BackgroundChannelRegistrar::GetOrCreate();
MOZ_ASSERT(registrar);
registrar->LinkHttpChannel(mChannel->ChannelId(), this);
if (mBgParent) {
return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
}
return mPromise.Ensure(__func__);
}
bool HttpChannelParent::ConnectChannel(const uint32_t& registrarId,
const bool& shouldIntercept) {
nsresult rv;
LOG(
("HttpChannelParent::ConnectChannel: Looking for a registered channel "
"[this=%p, id=%" PRIu32 "]\n",
this, registrarId));
nsCOMPtr<nsIChannel> channel;
rv = NS_LinkRedirectChannels(registrarId, this, getter_AddRefs(channel));
if (NS_FAILED(rv)) {
NS_ERROR("Could not find the http channel to connect its IPC parent");
// This makes the channel delete itself safely. It's the only thing
// we can do now, since this parent channel cannot be used and there is
// no other way to tell the child side there were something wrong.
Delete();
return true;
}
LOG((" found channel %p, rv=%08" PRIx32, channel.get(),
static_cast<uint32_t>(rv)));
mChannel = do_QueryObject(channel);
if (!mChannel) {
LOG((" but it's not HttpBaseChannel"));
Delete();
return true;
}
LOG((" and it is HttpBaseChannel %p", mChannel.get()));
RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
if (httpChannelImpl) {
httpChannelImpl->SetWarningReporter(this);
}
nsCOMPtr<nsINetworkInterceptController> controller;
NS_QueryNotificationCallbacks(channel, controller);
RefPtr<ParentChannelListener> parentListener = do_QueryObject(controller);
if (parentListener) {
// It's possible with DocumentChannel redirects that we failed to Suspend
// the nsHttpChannel, so it has delivered OnDataAvailable/OnStopRequest
// to the DocumentChannelParent, and then cleared the listener pointer.
// In that case the DocumentChannelParent will handle forwarding
// those messages to us so we don't need to add the listener again.
parentListener->SetupInterceptionAfterRedirect(shouldIntercept);
}
if (mPBOverride != kPBOverride_Unset) {
// redirected-to channel may not support PB
nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel = do_QueryObject(mChannel);
if (pbChannel) {
pbChannel->SetPrivate(mPBOverride == kPBOverride_Private ? true : false);
}
}
MOZ_ASSERT(!mBgParent);
MOZ_ASSERT(mPromise.IsEmpty());
// Waiting for background channel
RefPtr<HttpChannelParent> self = this;
WaitForBgParent()
->Then(
GetMainThreadSerialEventTarget(), __func__,
[self]() { self->mRequest.Complete(); },
[self](const nsresult& aResult) {
NS_ERROR("failed to establish the background channel");
self->mRequest.Complete();
})
->Track(mRequest);
return true;
}
mozilla::ipc::IPCResult HttpChannelParent::RecvSetPriority(
const int16_t& priority) {
LOG(("HttpChannelParent::RecvSetPriority [this=%p, priority=%d]\n", this,
priority));
AUTO_PROFILER_LABEL("HttpChannelParent::RecvSetPriority", NETWORK);
if (mChannel) {
mChannel->SetPriority(priority);
}
nsCOMPtr<nsISupportsPriority> priorityRedirectChannel =
do_QueryInterface(mRedirectChannel);
if (priorityRedirectChannel) priorityRedirectChannel->SetPriority(priority);
return IPC_OK();
}
mozilla::ipc::IPCResult HttpChannelParent::RecvSetClassOfService(
const uint32_t& cos) {
if (mChannel) {
mChannel->SetClassFlags(cos);
}
return IPC_OK();
}
mozilla::ipc::IPCResult HttpChannelParent::RecvSuspend() {
LOG(("HttpChannelParent::RecvSuspend [this=%p]\n", this));
if (mChannel) {
mChannel->Suspend();
}
return IPC_OK();
}
mozilla::ipc::IPCResult HttpChannelParent::RecvResume() {
LOG(("HttpChannelParent::RecvResume [this=%p]\n", this));
if (mChannel) {
mChannel->Resume();
}
return IPC_OK();
}
mozilla::ipc::IPCResult HttpChannelParent::RecvCancel(
const nsresult& status, const uint32_t& requestBlockingReason) {
LOG(("HttpChannelParent::RecvCancel [this=%p]\n", this));
// May receive cancel before channel has been constructed!
if (mChannel) {
mChannel->Cancel(status);
if (MOZ_UNLIKELY(requestBlockingReason !=
nsILoadInfo::BLOCKING_REASON_NONE)) {
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
loadInfo->SetRequestBlockingReason(requestBlockingReason);
}
// Once we receive |Cancel|, child will stop sending RecvBytesRead. Force
// the channel resumed if needed.
if (mSuspendedForFlowControl) {
LOG((" resume the channel due to e10s backpressure relief by cancel"));
Unused << mChannel->Resume();
mSuspendedForFlowControl = false;
}
} else if (!mIPCClosed) {
// Make sure that the child correctly delivers all stream listener
// notifications.
Unused << SendFailedAsyncOpen(status);
}
// We won't need flow control anymore. Toggle the flag to avoid |Suspend|
// since OnDataAvailable could be off-main-thread.
mCacheNeedFlowControlInitialized = true;
mNeedFlowControl = false;
return IPC_OK();
}
mozilla::ipc::IPCResult HttpChannelParent::RecvSetCacheTokenCachedCharset(
const nsCString& charset) {
if (mCacheEntry) mCacheEntry->SetMetaDataElement("charset", charset.get());
return IPC_OK();
}
mozilla::ipc::IPCResult HttpChannelParent::RecvRedirect2Verify(
const nsresult& aResult, const RequestHeaderTuples& changedHeaders,
const uint32_t& aSourceRequestBlockingReason,
const Maybe<ChildLoadInfoForwarderArgs>& aTargetLoadInfoForwarder,
const uint32_t& loadFlags, nsIReferrerInfo* aReferrerInfo,
const Maybe<URIParams>& aAPIRedirectURI,
const Maybe<CorsPreflightArgs>& aCorsPreflightArgs,
const bool& aChooseAppcache) {
LOG(("HttpChannelParent::RecvRedirect2Verify [this=%p result=%" PRIx32 "]\n",
this, static_cast<uint32_t>(aResult)));
// Result from the child. If something fails here, we might overwrite a
// success with a further failure.
nsresult result = aResult;
// Local results.
nsresult rv;
if (NS_SUCCEEDED(result)) {
nsCOMPtr<nsIHttpChannel> newHttpChannel =
do_QueryInterface(mRedirectChannel);
if (newHttpChannel) {
nsCOMPtr<nsIURI> apiRedirectUri = DeserializeURI(aAPIRedirectURI);
if (apiRedirectUri) {
rv = newHttpChannel->RedirectTo(apiRedirectUri);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
for (uint32_t i = 0; i < changedHeaders.Length(); i++) {
if (changedHeaders[i].mEmpty) {
rv = newHttpChannel->SetEmptyRequestHeader(changedHeaders[i].mHeader);
} else {
rv = newHttpChannel->SetRequestHeader(changedHeaders[i].mHeader,
changedHeaders[i].mValue,
changedHeaders[i].mMerge);
}
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
// A successfully redirected channel must have the LOAD_REPLACE flag.
MOZ_ASSERT(loadFlags & nsIChannel::LOAD_REPLACE);
if (loadFlags & nsIChannel::LOAD_REPLACE) {
newHttpChannel->SetLoadFlags(loadFlags);
}
if (aCorsPreflightArgs.isSome()) {
nsCOMPtr<nsIHttpChannelInternal> newInternalChannel =
do_QueryInterface(newHttpChannel);
MOZ_RELEASE_ASSERT(newInternalChannel);
const CorsPreflightArgs& args = aCorsPreflightArgs.ref();
newInternalChannel->SetCorsPreflightParameters(args.unsafeHeaders(),
false);
}
if (aReferrerInfo) {
RefPtr<HttpBaseChannel> baseChannel = do_QueryObject(newHttpChannel);
MOZ_ASSERT(baseChannel);
if (baseChannel) {
// Referrer header is computed in child no need to recompute here
rv = baseChannel->SetReferrerInfoInternal(aReferrerInfo, false, false,
true);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
do_QueryInterface(newHttpChannel);
if (appCacheChannel) {
bool setChooseAppCache = false;
if (aChooseAppcache) {
nsCOMPtr<nsIURI> uri;
// Using GetURI because this is what DoAsyncOpen uses.
newHttpChannel->GetURI(getter_AddRefs(uri));
OriginAttributes attrs;
StoragePrincipalHelper::GetOriginAttributes(
newHttpChannel, attrs, StoragePrincipalHelper::eRegularPrincipal);
nsCOMPtr<nsIPrincipal> principal =
BasePrincipal::CreateContentPrincipal(uri, attrs);
setChooseAppCache = NS_ShouldCheckAppCache(principal);
}
appCacheChannel->SetChooseApplicationCache(setChooseAppCache);
}
if (aTargetLoadInfoForwarder.isSome()) {
nsCOMPtr<nsILoadInfo> newLoadInfo = newHttpChannel->LoadInfo();
rv = MergeChildLoadInfoForwarder(aTargetLoadInfoForwarder.ref(),
newLoadInfo);
if (NS_FAILED(rv) && NS_SUCCEEDED(result)) {
result = rv;
}
}
}
}
// If the redirect is vetoed, reason is set on the source (current) channel's
// load info, so we must carry iver the change.
// The channel may have already been cleaned up, so there is nothing we can
// do.
if (MOZ_UNLIKELY(aSourceRequestBlockingReason !=
nsILoadInfo::BLOCKING_REASON_NONE) &&
mChannel) {
nsCOMPtr<nsILoadInfo> sourceLoadInfo = mChannel->LoadInfo();
sourceLoadInfo->SetRequestBlockingReason(aSourceRequestBlockingReason);
}
// Continue the verification procedure if child has veto the redirection.
if (NS_FAILED(result)) {
ContinueRedirect2Verify(result);
return IPC_OK();
}
// Wait for background channel ready on target channel
nsCOMPtr<nsIRedirectChannelRegistrar> redirectReg =
RedirectChannelRegistrar::GetOrCreate();
MOZ_ASSERT(redirectReg);
nsCOMPtr<nsIParentChannel> redirectParentChannel;
rv = redirectReg->GetParentChannel(mRedirectChannelId,
getter_AddRefs(redirectParentChannel));
MOZ_ASSERT(redirectParentChannel);
if (!redirectParentChannel) {
ContinueRedirect2Verify(rv);
return IPC_OK();
}
nsCOMPtr<nsIParentRedirectingChannel> redirectedParent =
do_QueryInterface(redirectParentChannel);
if (!redirectedParent) {
// Continue verification procedure if redirecting to non-Http protocol
ContinueRedirect2Verify(result);
return IPC_OK();
}
// Ask redirected channel if verification can proceed.
// ContinueRedirect2Verify will be invoked when redirected channel is ready.
redirectedParent->ContinueVerification(this);
return IPC_OK();
}
// from nsIParentRedirectingChannel
NS_IMETHODIMP
HttpChannelParent::ContinueVerification(
nsIAsyncVerifyRedirectReadyCallback* aCallback) {
LOG(("HttpChannelParent::ContinueVerification [this=%p callback=%p]\n", this,
aCallback));
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aCallback);
// Continue the verification procedure if background channel is ready.
if (mBgParent) {
aCallback->ReadyToVerify(NS_OK);
return NS_OK;
}
// ConnectChannel must be received before Redirect2Verify.
MOZ_ASSERT(!mPromise.IsEmpty());
// Otherwise, wait for the background channel.
nsCOMPtr<nsIAsyncVerifyRedirectReadyCallback> callback = aCallback;
WaitForBgParent()->Then(
GetMainThreadSerialEventTarget(), __func__,
[callback]() { callback->ReadyToVerify(NS_OK); },
[callback](const nsresult& aResult) {
NS_ERROR("failed to establish the background channel");
callback->ReadyToVerify(aResult);
});
return NS_OK;
}
void HttpChannelParent::ContinueRedirect2Verify(const nsresult& aResult) {
LOG(("HttpChannelParent::ContinueRedirect2Verify [this=%p result=%" PRIx32
"]\n",
this, static_cast<uint32_t>(aResult)));
if (!mRedirectCallback) {
// This should according the logic never happen, log the situation.
if (mReceivedRedirect2Verify) {
LOG(("RecvRedirect2Verify[%p]: Duplicate fire", this));
}
if (mSentRedirect1BeginFailed) {
LOG(("RecvRedirect2Verify[%p]: Send to child failed", this));
}
if ((mRedirectChannelId > 0) && NS_FAILED(aResult)) {
LOG(("RecvRedirect2Verify[%p]: Redirect failed", this));
}
if ((mRedirectChannelId > 0) && NS_SUCCEEDED(aResult)) {
LOG(("RecvRedirect2Verify[%p]: Redirect succeeded", this));
}
if (!mRedirectChannel) {
LOG(("RecvRedirect2Verify[%p]: Missing redirect channel", this));
}
NS_ERROR(
"Unexpcted call to HttpChannelParent::RecvRedirect2Verify, "
"mRedirectCallback null");
}
mReceivedRedirect2Verify = true;
if (mRedirectCallback) {
LOG(
("HttpChannelParent::ContinueRedirect2Verify call "
"OnRedirectVerifyCallback"
" [this=%p result=%" PRIx32 ", mRedirectCallback=%p]\n",
this, static_cast<uint32_t>(aResult), mRedirectCallback.get()));
mRedirectCallback->OnRedirectVerifyCallback(aResult);
mRedirectCallback = nullptr;
}
}
mozilla::ipc::IPCResult HttpChannelParent::RecvDocumentChannelCleanup(
const bool& clearCacheEntry) {
CleanupBackgroundChannel(); // Background channel can be closed.
mChannel = nullptr; // Reclaim some memory sooner.
if (clearCacheEntry) {
mCacheEntry = nullptr; // Else we'll block other channels reading same URI
}
return IPC_OK();
}
mozilla::ipc::IPCResult
HttpChannelParent::RecvMarkOfflineCacheEntryAsForeign() {
if (mOfflineForeignMarker) {
mOfflineForeignMarker->MarkAsForeign();
mOfflineForeignMarker = nullptr;
}
return IPC_OK();
}
class DivertDataAvailableEvent : public MainThreadChannelEvent {
public:
DivertDataAvailableEvent(HttpChannelParent* aParent, const nsCString& data,
const uint64_t& offset, const uint32_t& count)
: mParent(aParent), mData(data), mOffset(offset), mCount(count) {}
void Run() override {
mParent->DivertOnDataAvailable(mData, mOffset, mCount);
}
private:
HttpChannelParent* mParent;
nsCString mData;
uint64_t mOffset;
uint32_t mCount;
};
mozilla::ipc::IPCResult HttpChannelParent::RecvDivertOnDataAvailable(
const nsCString& data, const uint64_t& offset, const uint32_t& count) {
LOG(("HttpChannelParent::RecvDivertOnDataAvailable [this=%p]\n", this));
MOZ_ASSERT(mParentListener);
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot RecvDivertOnDataAvailable if diverting is not set!");
FailDiversion(NS_ERROR_UNEXPECTED);
return IPC_FAIL_NO_REASON(this);
}
// Drop OnDataAvailables if the parent was canceled already.
if (NS_FAILED(mStatus)) {
return IPC_OK();
}
mEventQ->RunOrEnqueue(
new DivertDataAvailableEvent(this, data, offset, count));
return IPC_OK();
}
void HttpChannelParent::DivertOnDataAvailable(const nsCString& data,
const uint64_t& offset,
const uint32_t& count) {
LOG(("HttpChannelParent::DivertOnDataAvailable [this=%p]\n", this));
MOZ_ASSERT(mParentListener);
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot DivertOnDataAvailable if diverting is not set!");
FailDiversion(NS_ERROR_UNEXPECTED);
return;
}
// Drop OnDataAvailables if the parent was canceled already.
if (NS_FAILED(mStatus)) {
return;
}
nsCOMPtr<nsIInputStream> stringStream;
nsresult rv =
NS_NewByteInputStream(getter_AddRefs(stringStream),
MakeSpan(data).To(count), NS_ASSIGNMENT_DEPEND);
if (NS_FAILED(rv)) {
if (mChannel) {
mChannel->Cancel(rv);
}
mStatus = rv;
return;
}
AutoEventEnqueuer ensureSerialDispatch(mEventQ);
rv = mParentListener->OnDataAvailable(mChannel, stringStream, offset, count);
stringStream->Close();
if (NS_FAILED(rv)) {
if (mChannel) {
mChannel->Cancel(rv);
}
mStatus = rv;
}
}
class DivertStopRequestEvent : public MainThreadChannelEvent {
public:
DivertStopRequestEvent(HttpChannelParent* aParent, const nsresult& statusCode)
: mParent(aParent), mStatusCode(statusCode) {}
void Run() override { mParent->DivertOnStopRequest(mStatusCode); }
private:
HttpChannelParent* mParent;
nsresult mStatusCode;
};
mozilla::ipc::IPCResult HttpChannelParent::RecvDivertOnStopRequest(
const nsresult& statusCode) {
LOG(("HttpChannelParent::RecvDivertOnStopRequest [this=%p]\n", this));
MOZ_ASSERT(mParentListener);
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot RecvDivertOnStopRequest if diverting is not set!");
FailDiversion(NS_ERROR_UNEXPECTED);
return IPC_FAIL_NO_REASON(this);
}
mEventQ->RunOrEnqueue(new DivertStopRequestEvent(this, statusCode));
return IPC_OK();
}
void HttpChannelParent::DivertOnStopRequest(const nsresult& statusCode) {
LOG(("HttpChannelParent::DivertOnStopRequest [this=%p]\n", this));
MOZ_ASSERT(mParentListener);
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot DivertOnStopRequest if diverting is not set!");
FailDiversion(NS_ERROR_UNEXPECTED);
return;
}
// Honor the channel's status even if the underlying transaction completed.
nsresult status = NS_FAILED(mStatus) ? mStatus : statusCode;
// Reset fake pending status in case OnStopRequest has already been called.
if (mChannel) {
mChannel->ForcePending(false);
}
AutoEventEnqueuer ensureSerialDispatch(mEventQ);
mParentListener->OnStopRequest(mChannel, status);
}
class DivertCompleteEvent : public MainThreadChannelEvent {
public:
explicit DivertCompleteEvent(HttpChannelParent* aParent) : mParent(aParent) {}
void Run() override { mParent->DivertComplete(); }
private:
HttpChannelParent* mParent;
};
mozilla::ipc::IPCResult HttpChannelParent::RecvDivertComplete() {
LOG(("HttpChannelParent::RecvDivertComplete [this=%p]\n", this));
MOZ_ASSERT(mParentListener);
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot RecvDivertComplete if diverting is not set!");
FailDiversion(NS_ERROR_UNEXPECTED);
return IPC_FAIL_NO_REASON(this);
}
mEventQ->RunOrEnqueue(new DivertCompleteEvent(this));
return IPC_OK();
}
void HttpChannelParent::DivertComplete() {
LOG(("HttpChannelParent::DivertComplete [this=%p]\n", this));
MOZ_ASSERT(mParentListener);
if (NS_WARN_IF(!mDivertingFromChild)) {
MOZ_ASSERT(mDivertingFromChild,
"Cannot DivertComplete if diverting is not set!");
FailDiversion(NS_ERROR_UNEXPECTED);
return;
}
nsresult rv = ResumeForDiversion();
if (NS_WARN_IF(NS_FAILED(rv))) {
FailDiversion(NS_ERROR_UNEXPECTED);
return;
}
mParentListener = nullptr;
}
void HttpChannelParent::MaybeFlushPendingDiversion() {
if (!mPendingDiversion) {
return;
}
mPendingDiversion = false;
nsresult rv = SuspendForDiversion();
if (NS_WARN_IF(NS_FAILED(rv))) {
return;
}
if (mDivertListener) {
DivertTo(mDivertListener);
}
}
void HttpChannelParent::ResponseSynthesized() {
// Suspend now even though the FinishSynthesizeResponse runnable has
// not executed. We want to suspend after we get far enough to trigger
// the synthesis, but not actually allow the nsHttpChannel to trigger
// any OnStartRequests().
if (mSuspendAfterSynthesizeResponse) {
mChannel->Suspend();
}
mWillSynthesizeResponse = false;
MaybeFlushPendingDiversion();
}
mozilla::ipc::IPCResult HttpChannelParent::RecvRemoveCorsPreflightCacheEntry(
const URIParams& uri,
const mozilla::ipc::PrincipalInfo& requestingPrincipal) {
nsCOMPtr<nsIURI> deserializedURI = DeserializeURI(uri);
if (!deserializedURI) {
return IPC_FAIL_NO_REASON(this);
}
auto principalOrErr = PrincipalInfoToPrincipal(requestingPrincipal);
if (NS_WARN_IF(principalOrErr.isErr())) {
return IPC_FAIL_NO_REASON(this);
}
nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
nsCORSListenerProxy::RemoveFromCorsPreflightCache(deserializedURI, principal);
return IPC_OK();
}
//-----------------------------------------------------------------------------
// HttpChannelParent::nsIRequestObserver
//-----------------------------------------------------------------------------
static ResourceTimingStructArgs GetTimingAttributes(HttpBaseChannel* aChannel) {
ResourceTimingStructArgs args;
TimeStamp timeStamp;
aChannel->GetDomainLookupStart(&timeStamp);
args.domainLookupStart() = timeStamp;
aChannel->GetDomainLookupEnd(&timeStamp);
args.domainLookupEnd() = timeStamp;
aChannel->GetConnectStart(&timeStamp);
args.connectStart() = timeStamp;
aChannel->GetTcpConnectEnd(&timeStamp);
args.tcpConnectEnd() = timeStamp;
aChannel->GetSecureConnectionStart(&timeStamp);
args.secureConnectionStart() = timeStamp;
aChannel->GetConnectEnd(&timeStamp);
args.connectEnd() = timeStamp;
aChannel->GetRequestStart(&timeStamp);
args.requestStart() = timeStamp;
aChannel->GetResponseStart(&timeStamp);
args.responseStart() = timeStamp;
aChannel->GetResponseEnd(&timeStamp);
args.responseEnd() = timeStamp;
aChannel->GetAsyncOpen(&timeStamp);
args.fetchStart() = timeStamp;
aChannel->GetRedirectStart(&timeStamp);
args.redirectStart() = timeStamp;
aChannel->GetRedirectEnd(&timeStamp);
args.redirectEnd() = timeStamp;
uint64_t size = 0;
aChannel->GetTransferSize(&size);
args.transferSize() = size;
aChannel->GetEncodedBodySize(&size);
args.encodedBodySize() = size;
// decodedBodySize can be computed in the child process so it doesn't need
// to be passed down.
nsCString protocolVersion;
aChannel->GetProtocolVersion(protocolVersion);
args.protocolVersion() = protocolVersion;
aChannel->GetCacheReadStart(&timeStamp);
args.cacheReadStart() = timeStamp;
aChannel->GetCacheReadEnd(&timeStamp);
args.cacheReadEnd() = timeStamp;
return args;
}
NS_IMETHODIMP
HttpChannelParent::OnStartRequest(nsIRequest* aRequest) {
nsresult rv;
LOG(("HttpChannelParent::OnStartRequest [this=%p, aRequest=%p]\n", this,
aRequest));
MOZ_ASSERT(NS_IsMainThread());
MOZ_RELEASE_ASSERT(!mDivertingFromChild,
"Cannot call OnStartRequest if diverting is set!");
Maybe<uint32_t> multiPartID;
bool isLastPartOfMultiPart = false;
DebugOnly<bool> isMultiPart = false;
RefPtr<HttpBaseChannel> chan = do_QueryObject(aRequest);
if (!chan) {
if (nsCOMPtr<nsIMultiPartChannel> multiPartChannel =
do_QueryInterface(aRequest)) {
isMultiPart = true;
nsCOMPtr<nsIChannel> baseChannel;
multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel));
chan = do_QueryObject(baseChannel);
uint32_t partID = 0;
multiPartChannel->GetPartID(&partID);
multiPartID = Some(partID);
multiPartChannel->GetIsLastPart(&isLastPartOfMultiPart);
}
}
MOZ_ASSERT(multiPartID || !isMultiPart, "Changed multi-part state?");
if (!chan) {
LOG((" aRequest is not HttpBaseChannel"));
NS_ERROR(
"Expecting only HttpBaseChannel as aRequest in "
"HttpChannelParent::OnStartRequest");
return NS_ERROR_UNEXPECTED;
}
mAfterOnStartRequestBegun = true;
// Todo: re-enable when bug 1589749 is fixed.
/*MOZ_ASSERT(mChannel == chan,
"HttpChannelParent getting OnStartRequest from a different "
"HttpBaseChannel instance");*/
HttpChannelOnStartRequestArgs args;
// Send down any permissions/cookies which are relevant to this URL if we are
// performing a document load. We can't do that if mIPCClosed is set.
if (!mIPCClosed) {
PContentParent* pcp = Manager()->Manager();
MOZ_ASSERT(pcp, "We should have a manager if our IPC isn't closed");
DebugOnly<nsresult> rv =
static_cast<ContentParent*>(pcp)->AboutToLoadHttpFtpDocumentForChild(
chan, &args.shouldWaitForOnStartRequestSent());
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
args.multiPartID() = multiPartID;
args.isLastPartOfMultiPart() = isLastPartOfMultiPart;
args.cacheExpirationTime() = nsICacheEntry::NO_EXPIRATION_TIME;
RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(chan);
if (httpChannelImpl) {
httpChannelImpl->IsFromCache(&args.isFromCache());
httpChannelImpl->IsRacing(&args.isRacing());
httpChannelImpl->GetCacheEntryId(&args.cacheEntryId());
httpChannelImpl->GetCacheTokenFetchCount(&args.cacheFetchCount());
httpChannelImpl->GetCacheTokenExpirationTime(&args.cacheExpirationTime());
httpChannelImpl->GetCacheTokenCachedCharset(args.cachedCharset());
mDataSentToChildProcess = httpChannelImpl->DataSentToChildProcess();
// If RCWN is enabled and cache wins, we can't use the ODA from socket
// process.
if (args.isRacing()) {
mDataSentToChildProcess =
httpChannelImpl->DataSentToChildProcess() && !args.isFromCache();
}
args.dataFromSocketProcess() = mDataSentToChildProcess;
bool loadedFromApplicationCache = false;
httpChannelImpl->GetLoadedFromApplicationCache(&loadedFromApplicationCache);
if (loadedFromApplicationCache) {
mOfflineForeignMarker.reset(
httpChannelImpl->GetOfflineCacheEntryAsForeignMarker());
nsCOMPtr<nsIApplicationCache> appCache;
httpChannelImpl->GetApplicationCache(getter_AddRefs(appCache));
nsCString appCacheGroupId;
nsCString appCacheClientId;
appCache->GetGroupID(args.appCacheGroupId());
appCache->GetClientID(args.appCacheClientId());
}
}
// Propagate whether or not conversion should occur from the parent-side
// channel to the child-side channel. Then disable the parent-side
// conversion so that it only occurs in the child.
Unused << chan->GetApplyConversion(&args.applyConversion());
chan->SetApplyConversion(false);
// If we've already applied the conversion (as can happen if we installed
// a multipart converted), then don't apply it again on the child.
if (chan->HasAppliedConversion()) {
args.applyConversion() = false;
}
chan->GetStatus(&args.channelStatus());
// Keep the cache entry for future use in RecvSetCacheTokenCachedCharset().
// It could be already released by nsHttpChannel at that time.
nsCOMPtr<nsISupports> cacheEntry;
if (httpChannelImpl) {
httpChannelImpl->GetCacheToken(getter_AddRefs(cacheEntry));
mCacheEntry = do_QueryInterface(cacheEntry);
args.cacheEntryAvailable() = mCacheEntry ? true : false;
httpChannelImpl->GetCacheKey(&args.cacheKey());
httpChannelImpl->GetAlternativeDataType(args.altDataType());
}
args.altDataLength() = chan->GetAltDataLength();
args.deliveringAltData() = chan->IsDeliveringAltData();
UpdateAndSerializeSecurityInfo(args.securityInfoSerialization());
chan->GetRedirectCount(&args.redirectCount());
nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
mozilla::ipc::LoadInfoToParentLoadInfoForwarder(loadInfo,
&args.loadInfoForwarder());
nsHttpResponseHead* responseHead = chan->GetResponseHead();
bool useResponseHead = !!responseHead;
nsHttpResponseHead cleanedUpResponseHead;
if (responseHead &&
(responseHead->HasHeader(nsHttp::Set_Cookie) || multiPartID)) {
cleanedUpResponseHead = *responseHead;
cleanedUpResponseHead.ClearHeader(nsHttp::Set_Cookie);
if (multiPartID) {
nsCOMPtr<nsIChannel> multiPartChannel = do_QueryInterface(aRequest);
// For the multipart channel, use the parsed subtype instead. Note that
// `chan` is the underlying base channel of the multipart channel in this
// case, which is different from `multiPartChannel`.
MOZ_ASSERT(multiPartChannel);
nsAutoCString contentType;
multiPartChannel->GetContentType(contentType);
cleanedUpResponseHead.SetContentType(contentType);
}
responseHead = &cleanedUpResponseHead;
}
if (!responseHead) {
responseHead = &cleanedUpResponseHead;
}
chan->GetIsResolvedByTRR(&args.isResolvedByTRR());
chan->GetAllRedirectsSameOrigin(&args.allRedirectsSameOrigin());
chan->GetCrossOriginOpenerPolicy(&args.openerPolicy());
args.selfAddr() = chan->GetSelfAddr();
args.peerAddr() = chan->GetPeerAddr();
args.timing() = GetTimingAttributes(mChannel);
if (mOverrideReferrerInfo) {
args.overrideReferrerInfo() = ToRefPtr(std::move(mOverrideReferrerInfo));
}
if (!mCookie.IsEmpty()) {
args.cookie() = std::move(mCookie);
}
nsHttpRequestHead* requestHead = chan->GetRequestHead();
// !!! We need to lock headers and please don't forget to unlock them !!!
requestHead->Enter();
nsHttpHeaderArray cleanedUpRequestHeaders;
bool cleanedUpRequest = false;
if (requestHead->HasHeader(nsHttp::Cookie)) {
cleanedUpRequestHeaders = requestHead->Headers();
cleanedUpRequestHeaders.ClearHeader(nsHttp::Cookie);
cleanedUpRequest = true;
}
rv = NS_OK;
if (mIPCClosed ||
!mBgParent->OnStartRequest(
*responseHead, useResponseHead,
cleanedUpRequest ? cleanedUpRequestHeaders : requestHead->Headers(),
args)) {
rv = NS_ERROR_UNEXPECTED;
}
requestHead->Exit();
// Need to wait for the cookies/permissions to content process, which is sent
// via PContent in AboutToLoadHttpFtpDocumentForChild. For multipart channel,
// send only one time since the cookies/permissions are the same.
if (NS_SUCCEEDED(rv) && args.shouldWaitForOnStartRequestSent() &&
multiPartID.valueOr(0) == 0) {
LOG(("HttpChannelParent::SendOnStartRequestSent\n"));
Unused << SendOnStartRequestSent();
}
return rv;
}
NS_IMETHODIMP
HttpChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) {
LOG(("HttpChannelParent::OnStopRequest: [this=%p aRequest=%p status=%" PRIx32
"]\n",
this, aRequest, static_cast<uint32_t>(aStatusCode)));
MOZ_ASSERT(NS_IsMainThread());
MOZ_RELEASE_ASSERT(!mDivertingFromChild,
"Cannot call OnStopRequest if diverting is set!");
RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
if (httpChannelImpl) {
httpChannelImpl->SetWarningReporter(nullptr);
}
nsHttpHeaderArray* responseTrailer = mChannel->GetResponseTrailers();
nsTArray<ConsoleReportCollected> consoleReports;
RefPtr<HttpBaseChannel> httpChannel = do_QueryObject(mChannel);
if (httpChannel) {
httpChannel->StealConsoleReports(consoleReports);
}
// Either IPC channel is closed or background channel
// is ready to send OnStopRequest.
MOZ_ASSERT(mIPCClosed || mBgParent);
if (mDataSentToChildProcess) {
if (mIPCClosed || !mBgParent ||
!mBgParent->OnConsoleReport(consoleReports)) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
// If we're handling a multi-part stream, then send this directly
// over PHttpChannel to make synchronization easier.
if (mIPCClosed || !mBgParent ||
!mBgParent->OnStopRequest(
aStatusCode, GetTimingAttributes(mChannel),
responseTrailer ? *responseTrailer : nsHttpHeaderArray(),
consoleReports)) {
return NS_ERROR_UNEXPECTED;
}
if (NeedFlowControl()) {
bool isLocal = false;
NetAddr peerAddr = mChannel->GetPeerAddr();
#if defined(XP_UNIX)
// Unix-domain sockets are always local.
isLocal = (peerAddr.raw.family == PR_AF_LOCAL);
#endif
isLocal = isLocal || IsLoopBackAddress(&peerAddr);
if (!isLocal) {
if (!mHasSuspendedByBackPressure) {
AccumulateCategorical(
Telemetry::LABELS_NETWORK_BACK_PRESSURE_SUSPENSION_RATE_V2::
NotSuspended);
} else {
AccumulateCategorical(
Telemetry::LABELS_NETWORK_BACK_PRESSURE_SUSPENSION_RATE_V2::
Suspended);
// Only analyze non-local suspended cases, which we are interested in.
nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
Telemetry::Accumulate(
Telemetry::NETWORK_BACK_PRESSURE_SUSPENSION_CP_TYPE,
loadInfo->InternalContentPolicyType());
}
} else {
if (!mHasSuspendedByBackPressure) {
AccumulateCategorical(
Telemetry::LABELS_NETWORK_BACK_PRESSURE_SUSPENSION_RATE_V2::
NotSuspendedLocal);
} else {
AccumulateCategorical(
Telemetry::LABELS_NETWORK_BACK_PRESSURE_SUSPENSION_RATE_V2::
SuspendedLocal);
}
}
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpChannelParent::nsIMultiPartChannelListener
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelParent::OnAfterLastPart(nsresult aStatus) {
LOG(("HttpChannelParent::OnAfterLastPart [this=%p]\n", this));
MOZ_ASSERT(NS_IsMainThread());
// If IPC channel is closed, there is nothing we can do. Just return NS_OK.
if (mIPCClosed) {
return NS_OK;
}
// If IPC channel is open, background channel should be ready to send
// OnAfterLastPart.
MOZ_ASSERT(mBgParent);
if (!mBgParent || !mBgParent->OnAfterLastPart(aStatus)) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpChannelParent::nsIStreamListener
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelParent::OnDataAvailable(nsIRequest* aRequest,
nsIInputStream* aInputStream,
uint64_t aOffset, uint32_t aCount) {
LOG(("HttpChannelParent::OnDataAvailable [this=%p aRequest=%p offset=%" PRIu64
" count=%" PRIu32 "]\n",
this, aRequest, aOffset, aCount));
MOZ_ASSERT(NS_IsMainThread());
MOZ_RELEASE_ASSERT(!mDivertingFromChild,
"Cannot call OnDataAvailable if diverting is set!");
if (mDataSentToChildProcess) {
uint32_t n;
return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &n);
}
nsresult channelStatus = NS_OK;
mChannel->GetStatus(&channelStatus);
nsresult transportStatus = NS_NET_STATUS_RECEIVING_FROM;
RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
if (httpChannelImpl) {
if (httpChannelImpl->IsReadingFromCache()) {
transportStatus = NS_NET_STATUS_READING;
}
}
nsCString data;
nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount);
if (NS_FAILED(rv)) {
return rv;
}
// Either IPC channel is closed or background channel
// is ready to send OnTransportAndData.
MOZ_ASSERT(mIPCClosed || mBgParent);
if (mIPCClosed || !mBgParent ||
!mBgParent->OnTransportAndData(channelStatus, transportStatus, aOffset,
aCount, data)) {
return NS_ERROR_UNEXPECTED;
}
int32_t count = static_cast<int32_t>(aCount);
if (NeedFlowControl()) {
// We're going to run out of sending window size
if (mSendWindowSize > 0 && mSendWindowSize <= count) {
MOZ_ASSERT(!mSuspendedForFlowControl);
LOG((" suspend the channel due to e10s backpressure"));
Unused << mChannel->Suspend();
mSuspendedForFlowControl = true;
mHasSuspendedByBackPressure = true;
} else if (!mResumedTimestamp.IsNull()) {
// Calculate the delay when the first packet arrived after resume
Telemetry::AccumulateTimeDelta(
Telemetry::NETWORK_BACK_PRESSURE_SUSPENSION_DELAY_TIME_MS,
mResumedTimestamp);
mResumedTimestamp = TimeStamp();
}
mSendWindowSize -= count;
}
return NS_OK;
}
bool HttpChannelParent::NeedFlowControl() {
if (mCacheNeedFlowControlInitialized) {
return mNeedFlowControl;
}
int64_t contentLength = -1;
RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(mChannel);
// By design, we won't trigger the flow control if
// a. pref-out
// b. the resource is from cache or partial cache
// c. the resource is small
// d. data will be sent from socket process to child process directly
// Note that we served the cached resource first for partical cache, which is
// ignored here since we only take the first ODA into consideration.
if (gHttpHandler->SendWindowSize() == 0 || !httpChannelImpl ||
httpChannelImpl->IsReadingFromCache() ||
NS_FAILED(httpChannelImpl->GetContentLength(&contentLength)) ||
contentLength < gHttpHandler->SendWindowSize() ||
mDataSentToChildProcess) {
mNeedFlowControl = false;
}
mCacheNeedFlowControlInitialized = true;
return mNeedFlowControl;
}
mozilla::ipc::IPCResult HttpChannelParent::RecvBytesRead(
const int32_t& aCount) {
// no more flow control after diviersion starts
if (!NeedFlowControl() || mDivertingFromChild) {
return IPC_OK();
}
LOG(("HttpChannelParent::RecvBytesRead [this=%p count=%" PRId32 "]\n", this,
aCount));
if (mSendWindowSize <= 0 && mSendWindowSize + aCount > 0) {
MOZ_ASSERT(mSuspendedForFlowControl);
LOG((" resume the channel due to e10s backpressure relief"));
Unused << mChannel->Resume();
mSuspendedForFlowControl = false;
mResumedTimestamp = TimeStamp::Now();
}
mSendWindowSize += aCount;
return IPC_OK();
}
mozilla::ipc::IPCResult HttpChannelParent::RecvOpenOriginalCacheInputStream() {
if (mIPCClosed) {
return IPC_OK();
}
AutoIPCStream autoStream;
if (mCacheEntry) {
nsCOMPtr<nsIInputStream> inputStream;
nsresult rv = mCacheEntry->OpenInputStream(0, getter_AddRefs(inputStream));
if (NS_SUCCEEDED(rv)) {
PContentParent* pcp = Manager()->Manager();
Unused << autoStream.Serialize(inputStream,
static_cast<ContentParent*>(pcp));
}
}
Unused << SendOriginalCacheInputStreamAvailable(
autoStream.TakeOptionalValue());
return IPC_OK();
}
mozilla::ipc::IPCResult HttpChannelParent::RecvOpenAltDataCacheInputStream(
const nsCString& aType) {
if (mIPCClosed) {
return IPC_OK();
}
AutoIPCStream autoStream;
if (mCacheEntry) {
nsCOMPtr<nsIInputStream> inputStream;
nsresult rv = mCacheEntry->OpenAlternativeInputStream(
aType, getter_AddRefs(inputStream));
if (NS_SUCCEEDED(rv)) {
PContentParent* pcp = Manager()->Manager();
Unused << autoStream.Serialize(inputStream,
static_cast<ContentParent*>(pcp));
}
}
Unused << SendAltDataCacheInputStreamAvailable(
autoStream.TakeOptionalValue());
return IPC_OK();
}
//-----------------------------------------------------------------------------
// HttpChannelParent::nsIProgressEventSink
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelParent::OnProgress(nsIRequest* aRequest, int64_t aProgress,
int64_t aProgressMax) {
LOG(("HttpChannelParent::OnProgress [this=%p progress=%" PRId64 "max=%" PRId64
"]\n",
this, aProgress, aProgressMax));
MOZ_ASSERT(NS_IsMainThread());
// If IPC channel is closed, there is nothing we can do. Just return NS_OK.
if (mIPCClosed) {
return NS_OK;
}
// If it indicates this precedes OnDataAvailable, child can derive the value
// in ODA.
if (mIgnoreProgress) {
mIgnoreProgress = false;
return NS_OK;
}
// If IPC channel is open, background channel should be ready to send
// OnProgress.
MOZ_ASSERT(mBgParent);
// Send OnProgress events to the child for data upload progress notifications
// (i.e. status == NS_NET_STATUS_SENDING_TO) or if the channel has
// LOAD_BACKGROUND set.
if (!mBgParent || !mBgParent->OnProgress(aProgress, aProgressMax)) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
NS_IMETHODIMP
HttpChannelParent::OnStatus(nsIRequest* aRequest, nsresult aStatus,
const char16_t* aStatusArg) {
LOG(("HttpChannelParent::OnStatus [this=%p status=%" PRIx32 "]\n", this,
static_cast<uint32_t>(aStatus)));
MOZ_ASSERT(NS_IsMainThread());
// If IPC channel is closed, there is nothing we can do. Just return NS_OK.
if (mIPCClosed) {
return NS_OK;
}
// If this precedes OnDataAvailable, transportStatus will be derived in ODA.
if (aStatus == NS_NET_S