diff options
Diffstat (limited to 'netwerk/protocol/http/HttpChannelParent.cpp')
-rw-r--r-- | netwerk/protocol/http/HttpChannelParent.cpp | 1821 |
1 files changed, 1821 insertions, 0 deletions
diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp new file mode 100644 index 000000000..51da1ec8c --- /dev/null +++ b/netwerk/protocol/http/HttpChannelParent.cpp @@ -0,0 +1,1821 @@ +/* -*- 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/ipc/FileDescriptorSetParent.h" +#include "mozilla/net/HttpChannelParent.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/TabParent.h" +#include "mozilla/net/NeckoParent.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "HttpChannelParentListener.h" +#include "nsHttpHandler.h" +#include "nsNetUtil.h" +#include "nsISupportsPriority.h" +#include "nsIAuthPromptProvider.h" +#include "nsSerializationHelper.h" +#include "nsISerializable.h" +#include "nsIAssociatedContentSecurity.h" +#include "nsIApplicationCacheService.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/ipc/URIUtils.h" +#include "SerializedLoadContext.h" +#include "nsIAuthInformation.h" +#include "nsIAuthPromptCallback.h" +#include "nsIContentPolicy.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "nsICachingChannel.h" +#include "mozilla/LoadInfo.h" +#include "nsQueryObject.h" +#include "mozilla/BasePrincipal.h" +#include "nsCORSListenerProxy.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsIPrompt.h" +#include "nsIWindowWatcher.h" +#include "nsIDocument.h" +#include "nsStringStream.h" + +using mozilla::BasePrincipal; +using namespace mozilla::dom; +using namespace mozilla::ipc; + +namespace mozilla { +namespace net { + +HttpChannelParent::HttpChannelParent(const PBrowserOrId& iframeEmbedding, + nsILoadContext* aLoadContext, + PBOverrideStatus aOverrideStatus) + : mIPCClosed(false) + , mStoredStatus(NS_OK) + , mStoredProgress(0) + , mStoredProgressMax(0) + , mSentRedirect1Begin(false) + , mSentRedirect1BeginFailed(false) + , mReceivedRedirect2Verify(false) + , mPBOverride(aOverrideStatus) + , mLoadContext(aLoadContext) + , mStatus(NS_OK) + , mPendingDiversion(false) + , mDivertingFromChild(false) + , mDivertedOnStartRequest(false) + , mSuspendedForDiversion(false) + , mSuspendAfterSynthesizeResponse(false) + , mWillSynthesizeResponse(false) + , mNestedFrameId(0) +{ + 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; + + if (iframeEmbedding.type() == PBrowserOrId::TPBrowserParent) { + mTabParent = static_cast<dom::TabParent*>(iframeEmbedding.get_PBrowserParent()); + } else { + mNestedFrameId = iframeEmbedding.get_TabId(); + } + + mEventQ = new ChannelEventQueue(static_cast<nsIParentRedirectingChannel*>(this)); +} + +HttpChannelParent::~HttpChannelParent() +{ + LOG(("Destroying HttpChannelParent [this=%p]\n", this)); +} + +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(); + } +} + +bool +HttpChannelParent::Init(const HttpChannelCreationArgs& aArgs) +{ + LOG(("HttpChannelParent::Init [this=%p]\n", this)); + switch (aArgs.type()) { + case HttpChannelCreationArgs::THttpChannelOpenArgs: + { + const HttpChannelOpenArgs& a = aArgs.get_HttpChannelOpenArgs(); + return DoAsyncOpen(a.uri(), a.original(), a.doc(), a.referrer(), + a.referrerPolicy(), a.apiRedirectTo(), a.topWindowURI(), + a.loadFlags(), a.requestHeaders(), + a.requestMethod(), a.uploadStream(), + a.uploadStreamHasHeaders(), a.priority(), a.classOfService(), + a.redirectionLimit(), a.allowPipelining(), a.allowSTS(), + a.thirdPartyFlags(), a.resumeAt(), a.startPos(), + a.entityID(), a.chooseApplicationCache(), + a.appCacheClientID(), a.allowSpdy(), a.allowAltSvc(), a.beConservative(), + a.loadInfo(), a.synthesizedResponseHead(), + a.synthesizedSecurityInfoSerialization(), + a.cacheKey(), a.requestContextID(), a.preflightArgs(), + a.initialRwin(), a.blockAuthPrompt(), + a.suspendAfterSynthesizeResponse(), + a.allowStaleCacheContent(), a.contentTypeHint(), + a.channelId(), a.contentWindowId(), a.preferredAlternativeType()); + } + case HttpChannelCreationArgs::THttpChannelConnectArgs: + { + const HttpChannelConnectArgs& cArgs = aArgs.get_HttpChannelConnectArgs(); + return ConnectChannel(cArgs.registrarId(), cArgs.shouldIntercept()); + } + default: + NS_NOTREACHED("unknown open type"); + return false; + } +} + +//----------------------------------------------------------------------------- +// 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_AMBIGUOUS(nsISupports, nsIParentRedirectingChannel) + if (aIID.Equals(NS_GET_IID(HttpChannelParent))) { + foundInterface = static_cast<nsIInterfaceRequestor*>(this); + } else +NS_INTERFACE_MAP_END + +//----------------------------------------------------------------------------- +// HttpChannelParent::nsIInterfaceRequestor +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelParent::GetInterface(const nsIID& aIID, void **result) +{ + if (aIID.Equals(NS_GET_IID(nsIAuthPromptProvider)) || + aIID.Equals(NS_GET_IID(nsISecureBrowserUI))) { + if (mTabParent) { + return mTabParent->QueryInterface(aIID, result); + } + } + + // Only support nsIAuthPromptProvider in Content process + if (XRE_IsParentProcess() && + aIID.Equals(NS_GET_IID(nsIAuthPromptProvider))) { + *result = nullptr; + 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; + } + + if (mTabParent && aIID.Equals(NS_GET_IID(nsIPrompt))) { + nsCOMPtr<Element> frameElement = mTabParent->GetOwnerElement(); + if (frameElement) { + nsCOMPtr<nsPIDOMWindowOuter> win =frameElement->OwnerDoc()->GetWindow(); + NS_ENSURE_TRUE(win, NS_ERROR_UNEXPECTED); + + nsresult rv; + nsCOMPtr<nsIWindowWatcher> wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + + if (NS_WARN_IF(!NS_SUCCEEDED(rv))) { + return rv; + } + + nsCOMPtr<nsIPrompt> prompt; + rv = wwatch->GetNewPrompter(win, getter_AddRefs(prompt)); + if (NS_WARN_IF(!NS_SUCCEEDED(rv))) { + return rv; + } + + prompt.forget(result); + return NS_OK; + } + } + + return QueryInterface(aIID, result); +} + +//----------------------------------------------------------------------------- +// HttpChannelParent::PHttpChannelParent +//----------------------------------------------------------------------------- + +void +HttpChannelParent::InvokeAsyncOpen(nsresult rv) +{ + if (NS_FAILED(rv)) { + Unused << SendFailedAsyncOpen(rv); + return; + } + + nsCOMPtr<nsILoadInfo> loadInfo; + rv = mChannel->GetLoadInfo(getter_AddRefs(loadInfo)); + if (NS_FAILED(rv)) { + Unused << SendFailedAsyncOpen(rv); + return; + } + if (loadInfo && loadInfo->GetEnforceSecurity()) { + rv = mChannel->AsyncOpen2(mParentListener); + } + else { + rv = mChannel->AsyncOpen(mParentListener, nullptr); + } + if (NS_FAILED(rv)) { + Unused << SendFailedAsyncOpen(rv); + } +} + +namespace { +class InvokeAsyncOpen : public Runnable +{ + nsMainThreadPtrHandle<nsIInterfaceRequestor> mChannel; + nsresult mStatus; +public: + InvokeAsyncOpen(const nsMainThreadPtrHandle<nsIInterfaceRequestor>& aChannel, + nsresult aStatus) + : mChannel(aChannel) + , mStatus(aStatus) + { + } + + NS_IMETHOD Run() + { + RefPtr<HttpChannelParent> channel = do_QueryObject(mChannel.get()); + channel->InvokeAsyncOpen(mStatus); + return NS_OK; + } +}; + +struct UploadStreamClosure { + nsMainThreadPtrHandle<nsIInterfaceRequestor> mChannel; + + explicit UploadStreamClosure(const nsMainThreadPtrHandle<nsIInterfaceRequestor>& aChannel) + : mChannel(aChannel) + { + } +}; + +void +UploadCopyComplete(void* aClosure, nsresult aStatus) { + // Called on the Stream Transport Service thread by NS_AsyncCopy + MOZ_ASSERT(!NS_IsMainThread()); + UniquePtr<UploadStreamClosure> closure(static_cast<UploadStreamClosure*>(aClosure)); + nsCOMPtr<nsIRunnable> event = new InvokeAsyncOpen(closure->mChannel, aStatus); + NS_DispatchToMainThread(event); +} +} // anonymous namespace + +bool +HttpChannelParent::DoAsyncOpen( const URIParams& aURI, + const OptionalURIParams& aOriginalURI, + const OptionalURIParams& aDocURI, + const OptionalURIParams& aReferrerURI, + const uint32_t& aReferrerPolicy, + const OptionalURIParams& aAPIRedirectToURI, + const OptionalURIParams& aTopWindowURI, + const uint32_t& aLoadFlags, + const RequestHeaderTuples& requestHeaders, + const nsCString& requestMethod, + const OptionalIPCStream& uploadStream, + const bool& uploadStreamHasHeaders, + const uint16_t& priority, + const uint32_t& classOfService, + const uint8_t& redirectionLimit, + const bool& allowPipelining, + 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 OptionalLoadInfoArgs& aLoadInfoArgs, + const OptionalHttpResponseHead& aSynthesizedResponseHead, + const nsCString& aSecurityInfoSerialization, + const uint32_t& aCacheKey, + const nsCString& aRequestContextID, + const OptionalCorsPreflightArgs& aCorsPreflightArgs, + const uint32_t& aInitialRwin, + const bool& aBlockAuthPrompt, + const bool& aSuspendAfterSynthesizeResponse, + const bool& aAllowStaleCacheContent, + const nsCString& aContentTypeHint, + const nsCString& aChannelId, + const uint64_t& aContentWindowId, + const nsCString& aPreferredAlternativeType) +{ + 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> referrerUri = DeserializeURI(aReferrerURI); + nsCOMPtr<nsIURI> apiRedirectToUri = DeserializeURI(aAPIRedirectToURI); + nsCOMPtr<nsIURI> topWindowUri = DeserializeURI(aTopWindowURI); + + LOG(("HttpChannelParent RecvAsyncOpen [this=%p uri=%s]\n", + this, uri->GetSpecOrDefault().get())); + + 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); + } + + NeckoOriginAttributes attrs; + rv = loadInfo->GetOriginAttributes(&attrs); + if (NS_FAILED(rv)) { + return SendFailedAsyncOpen(rv); + } + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewChannelInternal(getter_AddRefs(channel), uri, loadInfo, + nullptr, nullptr, aLoadFlags, ios); + + if (NS_FAILED(rv)) + return SendFailedAsyncOpen(rv); + + // This cast is safe since this is AsyncOpen specific to http. channel + // is ensured to be nsHttpChannel. + mChannel = static_cast<nsHttpChannel *>(channel.get()); + + // Set the channelId allocated in child to the parent instance + mChannel->SetChannelId(aChannelId); + mChannel->SetTopLevelContentWindowId(aContentWindowId); + + mChannel->SetWarningReporter(this); + mChannel->SetTimingEnabled(true); + if (mPBOverride != kPBOverride_Unset) { + mChannel->SetPrivate(mPBOverride == kPBOverride_Private ? true : false); + } + + if (doResumeAt) + mChannel->ResumeAt(startPos, entityID); + + if (originalUri) + mChannel->SetOriginalURI(originalUri); + if (docUri) + mChannel->SetDocumentURI(docUri); + if (referrerUri) + mChannel->SetReferrerWithPolicyInternal(referrerUri, aReferrerPolicy); + if (apiRedirectToUri) + mChannel->RedirectTo(apiRedirectToUri); + if (topWindowUri) + mChannel->SetTopWindowURI(topWindowUri); + if (aLoadFlags != nsIRequest::LOAD_NORMAL) + mChannel->SetLoadFlags(aLoadFlags); + + for (uint32_t i = 0; i < requestHeaders.Length(); i++) { + if (requestHeaders[i].mEmpty) { + mChannel->SetEmptyRequestHeader(requestHeaders[i].mHeader); + } else { + mChannel->SetRequestHeader(requestHeaders[i].mHeader, + requestHeaders[i].mValue, + requestHeaders[i].mMerge); + } + } + + mParentListener = new HttpChannelParentListener(this); + + mChannel->SetNotificationCallbacks(mParentListener); + + mChannel->SetRequestMethod(nsDependentCString(requestMethod.get())); + + if (aCorsPreflightArgs.type() == OptionalCorsPreflightArgs::TCorsPreflightArgs) { + const CorsPreflightArgs& args = aCorsPreflightArgs.get_CorsPreflightArgs(); + mChannel->SetCorsPreflightParameters(args.unsafeHeaders()); + } + + bool delayAsyncOpen = false; + nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(uploadStream); + if (stream) { + // FIXME: The fast path of using the existing stream currently only applies to streams + // that have had their entire contents serialized from the child at this point. + // Once bug 1294446 and bug 1294450 are fixed it is worth revisiting this heuristic. + nsCOMPtr<nsIIPCSerializableInputStream> completeStream = do_QueryInterface(stream); + if (!completeStream) { + delayAsyncOpen = true; + + // buffer size matches PSendStream transfer size. + const uint32_t kBufferSize = 32768; + + nsCOMPtr<nsIStorageStream> storageStream; + nsresult rv = NS_NewStorageStream(kBufferSize, UINT32_MAX, + getter_AddRefs(storageStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return SendFailedAsyncOpen(rv); + } + + nsCOMPtr<nsIInputStream> newUploadStream; + rv = storageStream->NewInputStream(0, getter_AddRefs(newUploadStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return SendFailedAsyncOpen(rv); + } + + nsCOMPtr<nsIOutputStream> sink; + rv = storageStream->GetOutputStream(0, getter_AddRefs(sink)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return SendFailedAsyncOpen(rv); + } + + nsCOMPtr<nsIEventTarget> target = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv) || !target) { + return SendFailedAsyncOpen(rv); + } + + nsCOMPtr<nsIInterfaceRequestor> iir = static_cast<nsIInterfaceRequestor*>(this); + nsMainThreadPtrHandle<nsIInterfaceRequestor> handle = + nsMainThreadPtrHandle<nsIInterfaceRequestor>( + new nsMainThreadPtrHolder<nsIInterfaceRequestor>(iir)); + UniquePtr<UploadStreamClosure> closure(new UploadStreamClosure(handle)); + + // Accumulate the stream contents as the child sends it. We will continue with + // the AsyncOpen process once the full stream has been received. + rv = NS_AsyncCopy(stream, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS, + kBufferSize, // copy segment size + UploadCopyComplete, closure.release()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return SendFailedAsyncOpen(rv); + } + + mChannel->InternalSetUploadStream(newUploadStream); + } else { + mChannel->InternalSetUploadStream(stream); + } + mChannel->SetUploadStreamHasHeaders(uploadStreamHasHeaders); + } + + if (aSynthesizedResponseHead.type() == OptionalHttpResponseHead::TnsHttpResponseHead) { + mParentListener->SetupInterception(aSynthesizedResponseHead.get_nsHttpResponseHead()); + mWillSynthesizeResponse = true; + mChannel->SetCouldBeSynthesized(); + + if (!aSecurityInfoSerialization.IsEmpty()) { + nsCOMPtr<nsISupports> secInfo; + NS_DeserializeObject(aSecurityInfoSerialization, getter_AddRefs(secInfo)); + mChannel->OverrideSecurityInfo(secInfo); + } + } else { + nsLoadFlags newLoadFlags; + mChannel->GetLoadFlags(&newLoadFlags); + newLoadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER; + mChannel->SetLoadFlags(newLoadFlags); + } + + nsCOMPtr<nsISupportsPRUint32> cacheKey = + do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return SendFailedAsyncOpen(rv); + } + + rv = cacheKey->SetData(aCacheKey); + if (NS_FAILED(rv)) { + return SendFailedAsyncOpen(rv); + } + + mChannel->SetCacheKey(cacheKey); + mChannel->PreferAlternativeDataType(aPreferredAlternativeType); + + mChannel->SetAllowStaleCacheContent(aAllowStaleCacheContent); + + mChannel->SetContentType(aContentTypeHint); + + if (priority != nsISupportsPriority::PRIORITY_NORMAL) { + mChannel->SetPriority(priority); + } + if (classOfService) { + mChannel->SetClassFlags(classOfService); + } + mChannel->SetRedirectionLimit(redirectionLimit); + mChannel->SetAllowPipelining(allowPipelining); + mChannel->SetAllowSTS(allowSTS); + mChannel->SetThirdPartyFlags(thirdPartyFlags); + mChannel->SetAllowSpdy(allowSpdy); + mChannel->SetAllowAltSvc(allowAltSvc); + mChannel->SetBeConservative(beConservative); + mChannel->SetInitialRwin(aInitialRwin); + mChannel->SetBlockAuthPrompt(aBlockAuthPrompt); + + nsCOMPtr<nsIApplicationCacheChannel> appCacheChan = + do_QueryObject(mChannel); + 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) { + NeckoOriginAttributes neckoAttrs; + NS_GetOriginAttributes(mChannel, neckoAttrs); + + PrincipalOriginAttributes attrs; + attrs.InheritFromNecko(neckoAttrs); + nsCOMPtr<nsIPrincipal> principal = + BasePrincipal::CreateCodebasePrincipal(uri, attrs); + + bool chooseAppCache = false; + // This works because we've already called SetNotificationCallbacks and + // done mPBOverride logic by this point. + chooseAppCache = NS_ShouldCheckAppCache(principal, NS_UsePrivateBrowsing(mChannel)); + + appCacheChan->SetChooseApplicationCache(chooseAppCache); + } + } + + nsID requestContextID; + requestContextID.Parse(aRequestContextID.BeginReading()); + mChannel->SetRequestContextID(requestContextID); + + mSuspendAfterSynthesizeResponse = aSuspendAfterSynthesizeResponse; + + if (!delayAsyncOpen) { + InvokeAsyncOpen(NS_OK); + } + + return true; +} + +bool +HttpChannelParent::ConnectChannel(const uint32_t& registrarId, const bool& shouldIntercept) +{ + nsresult rv; + + LOG(("HttpChannelParent::ConnectChannel: Looking for a registered channel " + "[this=%p, id=%lu]\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; + } + + // It's safe to cast here since the found parent-side real channel is ensured + // to be http (nsHttpChannel). ConnectChannel called from HttpChannelParent::Init + // can only be called for http channels. It's bound by ipdl. + mChannel = static_cast<nsHttpChannel*>(channel.get()); + LOG((" found channel %p, rv=%08x", mChannel.get(), rv)); + + nsCOMPtr<nsINetworkInterceptController> controller; + NS_QueryNotificationCallbacks(channel, controller); + RefPtr<HttpChannelParentListener> parentListener = do_QueryObject(controller); + MOZ_ASSERT(parentListener); + 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); + } + } + + return true; +} + +bool +HttpChannelParent::RecvSetPriority(const uint16_t& priority) +{ + LOG(("HttpChannelParent::RecvSetPriority [this=%p, priority=%u]\n", + this, priority)); + + if (mChannel) { + mChannel->SetPriority(priority); + } + + nsCOMPtr<nsISupportsPriority> priorityRedirectChannel = + do_QueryInterface(mRedirectChannel); + if (priorityRedirectChannel) + priorityRedirectChannel->SetPriority(priority); + + return true; +} + +bool +HttpChannelParent::RecvSetClassOfService(const uint32_t& cos) +{ + if (mChannel) { + mChannel->SetClassFlags(cos); + } + return true; +} + +bool +HttpChannelParent::RecvSuspend() +{ + LOG(("HttpChannelParent::RecvSuspend [this=%p]\n", this)); + + if (mChannel) { + mChannel->Suspend(); + } + return true; +} + +bool +HttpChannelParent::RecvResume() +{ + LOG(("HttpChannelParent::RecvResume [this=%p]\n", this)); + + if (mChannel) { + mChannel->Resume(); + } + return true; +} + +bool +HttpChannelParent::RecvCancel(const nsresult& status) +{ + LOG(("HttpChannelParent::RecvCancel [this=%p]\n", this)); + + // May receive cancel before channel has been constructed! + if (mChannel) { + mChannel->Cancel(status); + } + return true; +} + + +bool +HttpChannelParent::RecvSetCacheTokenCachedCharset(const nsCString& charset) +{ + if (mCacheEntry) + mCacheEntry->SetMetaDataElement("charset", charset.get()); + return true; +} + +bool +HttpChannelParent::RecvUpdateAssociatedContentSecurity(const int32_t& broken, + const int32_t& no) +{ + if (mAssociatedContentSecurity) { + mAssociatedContentSecurity->SetCountSubRequestsBrokenSecurity(broken); + mAssociatedContentSecurity->SetCountSubRequestsNoSecurity(no); + } + return true; +} + +bool +HttpChannelParent::RecvRedirect2Verify(const nsresult& result, + const RequestHeaderTuples& changedHeaders, + const uint32_t& loadFlags, + const OptionalURIParams& aAPIRedirectURI, + const OptionalCorsPreflightArgs& aCorsPreflightArgs, + const bool& aForceHSTSPriming, + const bool& aMixedContentWouldBlock, + const bool& aChooseAppcache) +{ + LOG(("HttpChannelParent::RecvRedirect2Verify [this=%p result=%x]\n", + this, result)); + nsresult rv; + if (NS_SUCCEEDED(result)) { + nsCOMPtr<nsIHttpChannel> newHttpChannel = + do_QueryInterface(mRedirectChannel); + + if (newHttpChannel) { + nsCOMPtr<nsIURI> apiRedirectUri = DeserializeURI(aAPIRedirectURI); + + if (apiRedirectUri) + newHttpChannel->RedirectTo(apiRedirectUri); + + for (uint32_t i = 0; i < changedHeaders.Length(); i++) { + if (changedHeaders[i].mEmpty) { + newHttpChannel->SetEmptyRequestHeader(changedHeaders[i].mHeader); + } else { + newHttpChannel->SetRequestHeader(changedHeaders[i].mHeader, + changedHeaders[i].mValue, + changedHeaders[i].mMerge); + } + } + + // 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.type() == OptionalCorsPreflightArgs::TCorsPreflightArgs) { + nsCOMPtr<nsIHttpChannelInternal> newInternalChannel = + do_QueryInterface(newHttpChannel); + MOZ_RELEASE_ASSERT(newInternalChannel); + const CorsPreflightArgs& args = aCorsPreflightArgs.get_CorsPreflightArgs(); + newInternalChannel->SetCorsPreflightParameters(args.unsafeHeaders()); + } + + if (aForceHSTSPriming) { + nsCOMPtr<nsILoadInfo> newLoadInfo; + rv = newHttpChannel->GetLoadInfo(getter_AddRefs(newLoadInfo)); + if (NS_SUCCEEDED(rv) && newLoadInfo) { + newLoadInfo->SetHSTSPriming(aMixedContentWouldBlock); + } + } + + nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel = + do_QueryInterface(newHttpChannel); + if (appCacheChannel) { + appCacheChannel->SetChooseApplicationCache(aChooseAppcache); + } + } + } + + 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 (mSentRedirect1Begin && NS_FAILED(result)) + LOG(("RecvRedirect2Verify[%p]: Redirect failed", this)); + if (mSentRedirect1Begin && NS_SUCCEEDED(result)) + 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::RecvRedirect2Verify call OnRedirectVerifyCallback" + " [this=%p result=%x, mRedirectCallback=%p]\n", + this, result, mRedirectCallback.get())); + mRedirectCallback->OnRedirectVerifyCallback(result); + mRedirectCallback = nullptr; + } + + return true; +} + +bool +HttpChannelParent::RecvDocumentChannelCleanup() +{ + // From now on only using mAssociatedContentSecurity. Free everything else. + mChannel = nullptr; // Reclaim some memory sooner. + mCacheEntry = nullptr; // Else we'll block other channels reading same URI + return true; +} + +bool +HttpChannelParent::RecvMarkOfflineCacheEntryAsForeign() +{ + if (mOfflineForeignMarker) { + mOfflineForeignMarker->MarkAsForeign(); + mOfflineForeignMarker = 0; + } + + return true; +} + +class DivertDataAvailableEvent : public ChannelEvent +{ +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() + { + mParent->DivertOnDataAvailable(mData, mOffset, mCount); + } + +private: + HttpChannelParent* mParent; + nsCString mData; + uint64_t mOffset; + uint32_t mCount; +}; + +bool +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 false; + } + + // Drop OnDataAvailables if the parent was canceled already. + if (NS_FAILED(mStatus)) { + return true; + } + + mEventQ->RunOrEnqueue(new DivertDataAvailableEvent(this, data, offset, + count)); + return true; +} + +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), data.get(), + count, NS_ASSIGNMENT_DEPEND); + if (NS_FAILED(rv)) { + if (mChannel) { + mChannel->Cancel(rv); + } + mStatus = rv; + return; + } + + AutoEventEnqueuer ensureSerialDispatch(mEventQ); + + rv = mParentListener->OnDataAvailable(mChannel, nullptr, stringStream, + offset, count); + stringStream->Close(); + if (NS_FAILED(rv)) { + if (mChannel) { + mChannel->Cancel(rv); + } + mStatus = rv; + } +} + +class DivertStopRequestEvent : public ChannelEvent +{ +public: + DivertStopRequestEvent(HttpChannelParent* aParent, + const nsresult& statusCode) + : mParent(aParent) + , mStatusCode(statusCode) + { + } + + void Run() { + mParent->DivertOnStopRequest(mStatusCode); + } + +private: + HttpChannelParent* mParent; + nsresult mStatusCode; +}; + +bool +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 false; + } + + mEventQ->RunOrEnqueue(new DivertStopRequestEvent(this, statusCode)); + return true; +} + +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, nullptr, status); +} + +class DivertCompleteEvent : public ChannelEvent +{ +public: + explicit DivertCompleteEvent(HttpChannelParent* aParent) + : mParent(aParent) + { + } + + void Run() { + mParent->DivertComplete(); + } + +private: + HttpChannelParent* mParent; +}; + +bool +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 false; + } + + mEventQ->RunOrEnqueue(new DivertCompleteEvent(this)); + return true; +} + +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); + } + + return; +} + +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(); +} + +bool +HttpChannelParent::RecvRemoveCorsPreflightCacheEntry(const URIParams& uri, + const mozilla::ipc::PrincipalInfo& requestingPrincipal) +{ + nsCOMPtr<nsIURI> deserializedURI = DeserializeURI(uri); + if (!deserializedURI) { + return false; + } + nsCOMPtr<nsIPrincipal> principal = + PrincipalInfoToPrincipal(requestingPrincipal); + if (!principal) { + return false; + } + nsCORSListenerProxy::RemoveFromCorsPreflightCache(deserializedURI, + principal); + return true; +} + +//----------------------------------------------------------------------------- +// HttpChannelParent::nsIRequestObserver +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelParent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext) +{ + LOG(("HttpChannelParent::OnStartRequest [this=%p, aRequest=%p]\n", + this, aRequest)); + + MOZ_RELEASE_ASSERT(!mDivertingFromChild, + "Cannot call OnStartRequest if diverting is set!"); + + // We can't cast here since the new channel can be a redirect to a different + // schema. We must query the channel implementation through a special method. + nsHttpChannel *chan = nullptr; + nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal(do_QueryInterface(aRequest)); + if (httpChannelInternal) { + chan = httpChannelInternal->QueryHttpChannelImpl(); + } + + if (!chan) { + LOG((" aRequest is not nsHttpChannel")); + NS_ERROR("Expecting only nsHttpChannel as aRequest in HttpChannelParent::OnStartRequest"); + return NS_ERROR_UNEXPECTED; + } + + MOZ_ASSERT(mChannel == chan, + "HttpChannelParent getting OnStartRequest from a different nsHttpChannel instance"); + + nsHttpResponseHead *responseHead = chan->GetResponseHead(); + nsHttpRequestHead *requestHead = chan->GetRequestHead(); + bool isFromCache = false; + chan->IsFromCache(&isFromCache); + uint32_t expirationTime = nsICacheEntry::NO_EXPIRATION_TIME; + chan->GetCacheTokenExpirationTime(&expirationTime); + nsCString cachedCharset; + chan->GetCacheTokenCachedCharset(cachedCharset); + + bool loadedFromApplicationCache; + chan->GetLoadedFromApplicationCache(&loadedFromApplicationCache); + if (loadedFromApplicationCache) { + mOfflineForeignMarker = chan->GetOfflineCacheEntryAsForeignMarker(); + nsCOMPtr<nsIApplicationCache> appCache; + chan->GetApplicationCache(getter_AddRefs(appCache)); + nsCString appCacheGroupId; + nsCString appCacheClientId; + appCache->GetGroupID(appCacheGroupId); + appCache->GetClientID(appCacheClientId); + if (mIPCClosed || + !SendAssociateApplicationCache(appCacheGroupId, appCacheClientId)) + { + return NS_ERROR_UNEXPECTED; + } + } + + nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(aRequest); + if (encodedChannel) + encodedChannel->SetApplyConversion(false); + + // Keep the cache entry for future use in RecvSetCacheTokenCachedCharset(). + // It could be already released by nsHttpChannel at that time. + nsCOMPtr<nsISupports> cacheEntry; + chan->GetCacheToken(getter_AddRefs(cacheEntry)); + mCacheEntry = do_QueryInterface(cacheEntry); + + nsresult channelStatus = NS_OK; + chan->GetStatus(&channelStatus); + + nsCString secInfoSerialization; + UpdateAndSerializeSecurityInfo(secInfoSerialization); + + uint16_t redirectCount = 0; + chan->GetRedirectCount(&redirectCount); + + nsCOMPtr<nsISupports> cacheKey; + chan->GetCacheKey(getter_AddRefs(cacheKey)); + uint32_t cacheKeyValue = 0; + if (cacheKey) { + nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(cacheKey); + if (!container) { + return NS_ERROR_ILLEGAL_VALUE; + } + + nsresult rv = container->GetData(&cacheKeyValue); + if (NS_FAILED(rv)) { + return rv; + } + } + + nsAutoCString altDataType; + chan->GetAlternativeDataType(altDataType); + + // !!! We need to lock headers and please don't forget to unlock them !!! + requestHead->Enter(); + nsresult rv = NS_OK; + if (mIPCClosed || + !SendOnStartRequest(channelStatus, + responseHead ? *responseHead : nsHttpResponseHead(), + !!responseHead, + requestHead->Headers(), + isFromCache, + mCacheEntry ? true : false, + expirationTime, cachedCharset, secInfoSerialization, + chan->GetSelfAddr(), chan->GetPeerAddr(), + redirectCount, + cacheKeyValue, + altDataType)) + { + rv = NS_ERROR_UNEXPECTED; + } + requestHead->Exit(); + return rv; +} + +NS_IMETHODIMP +HttpChannelParent::OnStopRequest(nsIRequest *aRequest, + nsISupports *aContext, + nsresult aStatusCode) +{ + LOG(("HttpChannelParent::OnStopRequest: [this=%p aRequest=%p status=%x]\n", + this, aRequest, aStatusCode)); + + MOZ_RELEASE_ASSERT(!mDivertingFromChild, + "Cannot call OnStopRequest if diverting is set!"); + ResourceTimingStruct timing; + mChannel->GetDomainLookupStart(&timing.domainLookupStart); + mChannel->GetDomainLookupEnd(&timing.domainLookupEnd); + mChannel->GetConnectStart(&timing.connectStart); + mChannel->GetConnectEnd(&timing.connectEnd); + mChannel->GetRequestStart(&timing.requestStart); + mChannel->GetResponseStart(&timing.responseStart); + mChannel->GetResponseEnd(&timing.responseEnd); + mChannel->GetAsyncOpen(&timing.fetchStart); + mChannel->GetRedirectStart(&timing.redirectStart); + mChannel->GetRedirectEnd(&timing.redirectEnd); + mChannel->GetTransferSize(&timing.transferSize); + mChannel->GetEncodedBodySize(&timing.encodedBodySize); + // decodedBodySize can be computed in the child process so it doesn't need + // to be passed down. + mChannel->GetProtocolVersion(timing.protocolVersion); + + mChannel->GetCacheReadStart(&timing.cacheReadStart); + mChannel->GetCacheReadEnd(&timing.cacheReadEnd); + + if (mIPCClosed || !SendOnStopRequest(aStatusCode, timing)) + return NS_ERROR_UNEXPECTED; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// HttpChannelParent::nsIStreamListener +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelParent::OnDataAvailable(nsIRequest *aRequest, + nsISupports *aContext, + nsIInputStream *aInputStream, + uint64_t aOffset, + uint32_t aCount) +{ + LOG(("HttpChannelParent::OnDataAvailable [this=%p aRequest=%p]\n", + this, aRequest)); + + MOZ_RELEASE_ASSERT(!mDivertingFromChild, + "Cannot call OnDataAvailable if diverting is set!"); + + nsresult channelStatus = NS_OK; + mChannel->GetStatus(&channelStatus); + + static uint32_t const kCopyChunkSize = 128 * 1024; + uint32_t toRead = std::min<uint32_t>(aCount, kCopyChunkSize); + + nsCString data; + if (!data.SetCapacity(toRead, fallible)) { + LOG((" out of memory!")); + return NS_ERROR_OUT_OF_MEMORY; + } + + while (aCount) { + nsresult rv = NS_ReadInputStreamToString(aInputStream, data, toRead); + if (NS_FAILED(rv)) { + return rv; + } + + // OnDataAvailable is always preceded by OnStatus/OnProgress calls that set + // mStoredStatus/mStoredProgress(Max) to appropriate values, unless + // LOAD_BACKGROUND set. In that case, they'll have garbage values, but + // child doesn't use them. + if (mIPCClosed || !SendOnTransportAndData(channelStatus, mStoredStatus, + mStoredProgress, mStoredProgressMax, + aOffset, toRead, data)) { + return NS_ERROR_UNEXPECTED; + } + + aOffset += toRead; + aCount -= toRead; + toRead = std::min<uint32_t>(aCount, kCopyChunkSize); + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// HttpChannelParent::nsIProgressEventSink +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelParent::OnProgress(nsIRequest *aRequest, + nsISupports *aContext, + int64_t aProgress, + int64_t aProgressMax) +{ + // OnStatus has always just set mStoredStatus. If it indicates this precedes + // OnDataAvailable, store and ODA will send to child. + if (mStoredStatus == NS_NET_STATUS_RECEIVING_FROM || + mStoredStatus == NS_NET_STATUS_READING) + { + mStoredProgress = aProgress; + mStoredProgressMax = aProgressMax; + } else { + // 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 (mIPCClosed || !SendOnProgress(aProgress, aProgressMax)) + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelParent::OnStatus(nsIRequest *aRequest, + nsISupports *aContext, + nsresult aStatus, + const char16_t *aStatusArg) +{ + // If this precedes OnDataAvailable, store and ODA will send to child. + if (aStatus == NS_NET_STATUS_RECEIVING_FROM || + aStatus == NS_NET_STATUS_READING) + { + mStoredStatus = aStatus; + return NS_OK; + } + // Otherwise, send to child now + if (mIPCClosed || !SendOnStatus(aStatus)) + return NS_ERROR_UNEXPECTED; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// HttpChannelParent::nsIParentChannel +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelParent::SetParentListener(HttpChannelParentListener* aListener) +{ + LOG(("HttpChannelParent::SetParentListener [this=%p aListener=%p]\n", + this, aListener)); + MOZ_ASSERT(aListener); + MOZ_ASSERT(!mParentListener, "SetParentListener should only be called for " + "new HttpChannelParents after a redirect, when " + "mParentListener is null."); + mParentListener = aListener; + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelParent::NotifyTrackingProtectionDisabled() +{ + if (!mIPCClosed) + Unused << SendNotifyTrackingProtectionDisabled(); + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelParent::Delete() +{ + if (!mIPCClosed) + Unused << DoSendDeleteSelf(); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// HttpChannelParent::nsIParentRedirectingChannel +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +HttpChannelParent::StartRedirect(uint32_t registrarId, + nsIChannel* newChannel, + uint32_t redirectFlags, + nsIAsyncVerifyRedirectCallback* callback) +{ + LOG(("HttpChannelParent::StartRedirect [this=%p, registrarId=%lu " + "newChannel=%p callback=%p]\n", this, registrarId, newChannel, + callback)); + + if (mIPCClosed) + return NS_BINDING_ABORTED; + + nsCOMPtr<nsIURI> newURI; + newChannel->GetURI(getter_AddRefs(newURI)); + + URIParams uriParams; + SerializeURI(newURI, uriParams); + + nsCString secInfoSerialization; + UpdateAndSerializeSecurityInfo(secInfoSerialization); + + // If the channel is a HTTP channel, we also want to inform the child + // about the parent's channelId attribute, so that both parent and child + // share the same ID. Useful for monitoring channel activity in devtools. + nsAutoCString channelId; + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel); + if (httpChannel) { + nsresult rv = httpChannel->GetChannelId(channelId); + NS_ENSURE_SUCCESS(rv, NS_BINDING_ABORTED); + } + + nsHttpResponseHead *responseHead = mChannel->GetResponseHead(); + bool result = false; + if (!mIPCClosed) { + result = SendRedirect1Begin(registrarId, uriParams, redirectFlags, + responseHead ? *responseHead + : nsHttpResponseHead(), + secInfoSerialization, + channelId); + } + if (!result) { + // Bug 621446 investigation + mSentRedirect1BeginFailed = true; + return NS_BINDING_ABORTED; + } + + // Bug 621446 investigation + mSentRedirect1Begin = true; + + // Result is handled in RecvRedirect2Verify above + + mRedirectChannel = newChannel; + mRedirectCallback = callback; + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelParent::CompleteRedirect(bool succeeded) +{ + LOG(("HttpChannelParent::CompleteRedirect [this=%p succeeded=%d]\n", + this, succeeded)); + + if (succeeded && !mIPCClosed) { + // TODO: check return value: assume child dead if failed + Unused << SendRedirect3Complete(); + } + + mRedirectChannel = nullptr; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// HttpChannelParent::ADivertableParentChannel +//----------------------------------------------------------------------------- +nsresult +HttpChannelParent::SuspendForDiversion() +{ + LOG(("HttpChannelParent::SuspendForDiversion [this=%p]\n", this)); + MOZ_ASSERT(mChannel); + MOZ_ASSERT(mParentListener); + + // If we're in the process of opening a synthesized response, we must wait + // to perform the diversion. Some of our diversion listeners clear callbacks + // which breaks the synthesis process. + if (mWillSynthesizeResponse) { + mPendingDiversion = true; + return NS_OK; + } + + if (NS_WARN_IF(mDivertingFromChild)) { + MOZ_ASSERT(!mDivertingFromChild, "Already suspended for diversion!"); + return NS_ERROR_UNEXPECTED; + } + + // MessageDiversionStarted call will suspend mEventQ as many times as the + // channel has been suspended, so that channel and this queue are in sync. + mChannel->MessageDiversionStarted(this); + + nsresult rv = NS_OK; + + // Try suspending the channel. Allow it to fail, since OnStopRequest may have + // been called and thus the channel may not be pending. If we've already + // automatically suspended after synthesizing the response, then we don't + // need to suspend again here. + if (!mSuspendAfterSynthesizeResponse) { + // We need to suspend only nsHttpChannel (i.e. we should not suspend + // mEventQ). Therefore we call mChannel->SuspendInternal() and not + // mChannel->Suspend(). + // We are suspending only nsHttpChannel here because we want to stop + // OnDataAvailable until diversion is over. At the same time we should + // send the diverted OnDataAvailable-s to the listeners and not queue them + // in mEventQ. + rv = mChannel->SuspendInternal(); + MOZ_ASSERT(NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_AVAILABLE); + mSuspendedForDiversion = NS_SUCCEEDED(rv); + } else { + // Take ownership of the automatic suspend that occurred after synthesizing + // the response. + mSuspendedForDiversion = true; + + // If mSuspendAfterSynthesizeResponse is true channel has been already + // suspended. From comment above mSuspendedForDiversion takes the ownership + // of this suspend, therefore mEventQ should not be suspened so we need to + // resume it once. + mEventQ->Resume(); + } + + rv = mParentListener->SuspendForDiversion(); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + // Once this is set, no more OnStart/OnData/OnStop callbacks should be sent + // to the child. + mDivertingFromChild = true; + + return NS_OK; +} + +nsresult +HttpChannelParent::SuspendMessageDiversion() +{ + LOG(("HttpChannelParent::SuspendMessageDiversion [this=%p]", this)); + // This only needs to suspend message queue. + mEventQ->Suspend(); + return NS_OK; +} + +nsresult +HttpChannelParent::ResumeMessageDiversion() +{ + LOG(("HttpChannelParent::SuspendMessageDiversion [this=%p]", this)); + // This only needs to resumes message queue. + mEventQ->Resume(); + return NS_OK; +} + +/* private, supporting function for ADivertableParentChannel */ +nsresult +HttpChannelParent::ResumeForDiversion() +{ + LOG(("HttpChannelParent::ResumeForDiversion [this=%p]\n", this)); + MOZ_ASSERT(mChannel); + if (NS_WARN_IF(!mDivertingFromChild)) { + MOZ_ASSERT(mDivertingFromChild, + "Cannot ResumeForDiversion if not diverting!"); + return NS_ERROR_UNEXPECTED; + } + + mChannel->MessageDiversionStop(); + + if (mSuspendedForDiversion) { + // The nsHttpChannel will deliver remaining OnData/OnStop for the transfer. + nsresult rv = mChannel->ResumeInternal(); + if (NS_WARN_IF(NS_FAILED(rv))) { + FailDiversion(NS_ERROR_UNEXPECTED, true); + return rv; + } + mSuspendedForDiversion = false; + } + + if (NS_WARN_IF(mIPCClosed || !DoSendDeleteSelf())) { + FailDiversion(NS_ERROR_UNEXPECTED); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +void +HttpChannelParent::DivertTo(nsIStreamListener *aListener) +{ + LOG(("HttpChannelParent::DivertTo [this=%p aListener=%p]\n", + this, aListener)); + MOZ_ASSERT(mParentListener); + + // If we're in the process of opening a synthesized response, we must wait + // to perform the diversion. Some of our diversion listeners clear callbacks + // which breaks the synthesis process. + if (mWillSynthesizeResponse) { + // We should already have started pending the diversion when + // SuspendForDiversion() was called. + MOZ_ASSERT(mPendingDiversion); + mDivertListener = aListener; + return; + } + + if (NS_WARN_IF(!mDivertingFromChild)) { + MOZ_ASSERT(mDivertingFromChild, + "Cannot DivertTo new listener if diverting is not set!"); + return; + } + + mDivertListener = aListener; + + // Call OnStartRequest and SendDivertMessages asynchronously to avoid + // reentering client context. + NS_DispatchToCurrentThread( + NewRunnableMethod(this, &HttpChannelParent::StartDiversion)); + return; +} + +void +HttpChannelParent::StartDiversion() +{ + LOG(("HttpChannelParent::StartDiversion [this=%p]\n", this)); + if (NS_WARN_IF(!mDivertingFromChild)) { + MOZ_ASSERT(mDivertingFromChild, + "Cannot StartDiversion if diverting is not set!"); + return; + } + + // Fake pending status in case OnStopRequest has already been called. + if (mChannel) { + mChannel->ForcePending(true); + } + + { + AutoEventEnqueuer ensureSerialDispatch(mEventQ); + + // Call OnStartRequest for the "DivertTo" listener. + nsresult rv = mDivertListener->OnStartRequest(mChannel, nullptr); + if (NS_FAILED(rv)) { + if (mChannel) { + mChannel->Cancel(rv); + } + mStatus = rv; + } + } + mDivertedOnStartRequest = true; + + // After OnStartRequest has been called, setup content decoders if needed. + // + // Create a content conversion chain based on mDivertListener and update + // mDivertListener. + nsCOMPtr<nsIStreamListener> converterListener; + mChannel->DoApplyContentConversions(mDivertListener, + getter_AddRefs(converterListener)); + if (converterListener) { + mDivertListener = converterListener.forget(); + } + + // Now mParentListener can be diverted to mDivertListener. + DebugOnly<nsresult> rvdbg = mParentListener->DivertTo(mDivertListener); + MOZ_ASSERT(NS_SUCCEEDED(rvdbg)); + mDivertListener = nullptr; + + if (NS_WARN_IF(mIPCClosed || !SendFlushedForDiversion())) { + FailDiversion(NS_ERROR_UNEXPECTED); + return; + } + + // The listener chain should now be setup; tell HttpChannelChild to divert + // the OnDataAvailables and OnStopRequest to this HttpChannelParent. + if (NS_WARN_IF(mIPCClosed || !SendDivertMessages())) { + FailDiversion(NS_ERROR_UNEXPECTED); + return; + } +} + +class HTTPFailDiversionEvent : public Runnable +{ +public: + HTTPFailDiversionEvent(HttpChannelParent *aChannelParent, + nsresult aErrorCode, + bool aSkipResume) + : mChannelParent(aChannelParent) + , mErrorCode(aErrorCode) + , mSkipResume(aSkipResume) + { + MOZ_RELEASE_ASSERT(aChannelParent); + MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode)); + } + NS_IMETHOD Run() override + { + mChannelParent->NotifyDiversionFailed(mErrorCode, mSkipResume); + return NS_OK; + } +private: + RefPtr<HttpChannelParent> mChannelParent; + nsresult mErrorCode; + bool mSkipResume; +}; + +void +HttpChannelParent::FailDiversion(nsresult aErrorCode, + bool aSkipResume) +{ + MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode)); + MOZ_RELEASE_ASSERT(mDivertingFromChild); + MOZ_RELEASE_ASSERT(mParentListener); + MOZ_RELEASE_ASSERT(mChannel); + + NS_DispatchToCurrentThread( + new HTTPFailDiversionEvent(this, aErrorCode, aSkipResume)); +} + +void +HttpChannelParent::NotifyDiversionFailed(nsresult aErrorCode, + bool aSkipResume) +{ + LOG(("HttpChannelParent::NotifyDiversionFailed [this=%p aErrorCode=%x]\n", + this, aErrorCode)); + MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode)); + MOZ_RELEASE_ASSERT(mDivertingFromChild); + MOZ_RELEASE_ASSERT(mParentListener); + MOZ_RELEASE_ASSERT(mChannel); + + mChannel->Cancel(aErrorCode); + + mChannel->ForcePending(false); + + bool isPending = false; + nsresult rv = mChannel->IsPending(&isPending); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + + // Resume only if we suspended earlier. + if (mSuspendedForDiversion) { + mChannel->ResumeInternal(); + } + // Channel has already sent OnStartRequest to the child, so ensure that we + // call it here if it hasn't already been called. + if (!mDivertedOnStartRequest) { + mChannel->ForcePending(true); + mParentListener->OnStartRequest(mChannel, nullptr); + mChannel->ForcePending(false); + } + // If the channel is pending, it will call OnStopRequest itself; otherwise, do + // it here. + if (!isPending) { + mParentListener->OnStopRequest(mChannel, nullptr, aErrorCode); + } + mParentListener = nullptr; + mChannel = nullptr; + + if (!mIPCClosed) { + Unused << DoSendDeleteSelf(); + } +} + +nsresult +HttpChannelParent::OpenAlternativeOutputStream(const nsACString & type, nsIOutputStream * *_retval) +{ + // We need to make sure the child does not call SendDocumentChannelCleanup() + // before opening the altOutputStream, because that clears mCacheEntry. + if (!mCacheEntry) { + return NS_ERROR_NOT_AVAILABLE; + } + return mCacheEntry->OpenAlternativeOutputStream(type, _retval); +} + +NS_IMETHODIMP +HttpChannelParent::GetAuthPrompt(uint32_t aPromptReason, const nsIID& iid, + void** aResult) +{ + nsCOMPtr<nsIAuthPrompt2> prompt = + new NeckoParent::NestedFrameAuthPrompt(Manager(), mNestedFrameId); + prompt.forget(aResult); + return NS_OK; +} + +void +HttpChannelParent::UpdateAndSerializeSecurityInfo(nsACString& aSerializedSecurityInfoOut) +{ + nsCOMPtr<nsISupports> secInfoSupp; + mChannel->GetSecurityInfo(getter_AddRefs(secInfoSupp)); + if (secInfoSupp) { + mAssociatedContentSecurity = do_QueryInterface(secInfoSupp); + nsCOMPtr<nsISerializable> secInfoSer = do_QueryInterface(secInfoSupp); + if (secInfoSer) { + NS_SerializeToString(secInfoSer, aSerializedSecurityInfoOut); + } + } +} + +bool +HttpChannelParent::DoSendDeleteSelf() +{ + bool rv = SendDeleteSelf(); + mIPCClosed = true; + return rv; +} + +bool +HttpChannelParent::RecvDeletingChannel() +{ + // We need to ensure that the parent channel will not be sending any more IPC + // messages after this, as the child is going away. DoSendDeleteSelf will + // set mIPCClosed = true; + return DoSendDeleteSelf(); +} + +bool +HttpChannelParent::RecvFinishInterceptedRedirect() +{ + // We make sure not to send any more messages until the IPC channel is torn + // down by the child. + mIPCClosed = true; + return SendFinishInterceptedRedirect(); +} + +//----------------------------------------------------------------------------- +// HttpChannelSecurityWarningReporter +//----------------------------------------------------------------------------- + +nsresult +HttpChannelParent::ReportSecurityMessage(const nsAString& aMessageTag, + const nsAString& aMessageCategory) +{ + if (mIPCClosed || + NS_WARN_IF(!SendReportSecurityMessage(nsString(aMessageTag), + nsString(aMessageCategory)))) { + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelParent::IssueWarning(uint32_t aWarning, bool aAsError) +{ + Unused << SendIssueDeprecationWarning(aWarning, aAsError); + return NS_OK; +} + +} // namespace net +} // namespace mozilla |