408 lines
13 KiB
C++
408 lines
13 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* 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 "HttpChannelParentListener.h"
|
|
#include "mozilla/net/HttpChannelParent.h"
|
|
#include "mozilla/Unused.h"
|
|
#include "nsIAuthPrompt.h"
|
|
#include "nsIAuthPrompt2.h"
|
|
#include "nsIHttpEventSink.h"
|
|
#include "nsIHttpHeaderVisitor.h"
|
|
#include "nsIRedirectChannelRegistrar.h"
|
|
#include "nsIPromptFactory.h"
|
|
#include "nsIWindowWatcher.h"
|
|
#include "nsQueryObject.h"
|
|
|
|
using mozilla::Unused;
|
|
|
|
namespace mozilla {
|
|
namespace net {
|
|
|
|
HttpChannelParentListener::HttpChannelParentListener(HttpChannelParent* aInitialChannel)
|
|
: mNextListener(aInitialChannel)
|
|
, mRedirectChannelId(0)
|
|
, mSuspendedForDiversion(false)
|
|
, mShouldIntercept(false)
|
|
, mShouldSuspendIntercept(false)
|
|
{
|
|
}
|
|
|
|
HttpChannelParentListener::~HttpChannelParentListener()
|
|
{
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpChannelParentListener::nsISupports
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMPL_ADDREF(HttpChannelParentListener)
|
|
NS_IMPL_RELEASE(HttpChannelParentListener)
|
|
NS_INTERFACE_MAP_BEGIN(HttpChannelParentListener)
|
|
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
|
|
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
|
|
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
|
|
NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
|
|
NS_INTERFACE_MAP_ENTRY(nsIRedirectResultListener)
|
|
NS_INTERFACE_MAP_ENTRY(nsINetworkInterceptController)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor)
|
|
if (aIID.Equals(NS_GET_IID(HttpChannelParentListener))) {
|
|
foundInterface = static_cast<nsIInterfaceRequestor*>(this);
|
|
} else
|
|
NS_INTERFACE_MAP_END
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpChannelParentListener::nsIRequestObserver
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpChannelParentListener::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext)
|
|
{
|
|
MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
|
|
"Cannot call OnStartRequest if suspended for diversion!");
|
|
|
|
if (!mNextListener)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
LOG(("HttpChannelParentListener::OnStartRequest [this=%p]\n", this));
|
|
return mNextListener->OnStartRequest(aRequest, aContext);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpChannelParentListener::OnStopRequest(nsIRequest *aRequest,
|
|
nsISupports *aContext,
|
|
nsresult aStatusCode)
|
|
{
|
|
MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
|
|
"Cannot call OnStopRequest if suspended for diversion!");
|
|
|
|
if (!mNextListener)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
LOG(("HttpChannelParentListener::OnStopRequest: [this=%p status=%ul]\n",
|
|
this, aStatusCode));
|
|
nsresult rv = mNextListener->OnStopRequest(aRequest, aContext, aStatusCode);
|
|
|
|
mNextListener = nullptr;
|
|
return rv;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpChannelParentListener::nsIStreamListener
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpChannelParentListener::OnDataAvailable(nsIRequest *aRequest,
|
|
nsISupports *aContext,
|
|
nsIInputStream *aInputStream,
|
|
uint64_t aOffset,
|
|
uint32_t aCount)
|
|
{
|
|
MOZ_RELEASE_ASSERT(!mSuspendedForDiversion,
|
|
"Cannot call OnDataAvailable if suspended for diversion!");
|
|
|
|
if (!mNextListener)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
LOG(("HttpChannelParentListener::OnDataAvailable [this=%p]\n", this));
|
|
return mNextListener->OnDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpChannelParentListener::nsIInterfaceRequestor
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpChannelParentListener::GetInterface(const nsIID& aIID, void **result)
|
|
{
|
|
if (aIID.Equals(NS_GET_IID(nsIChannelEventSink)) ||
|
|
aIID.Equals(NS_GET_IID(nsIHttpEventSink)) ||
|
|
aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) ||
|
|
aIID.Equals(NS_GET_IID(nsIRedirectResultListener)))
|
|
{
|
|
return QueryInterface(aIID, result);
|
|
}
|
|
|
|
nsCOMPtr<nsIInterfaceRequestor> ir;
|
|
if (mNextListener &&
|
|
NS_SUCCEEDED(CallQueryInterface(mNextListener.get(),
|
|
getter_AddRefs(ir))))
|
|
{
|
|
return ir->GetInterface(aIID, result);
|
|
}
|
|
|
|
if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) ||
|
|
aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) {
|
|
nsresult rv;
|
|
nsCOMPtr<nsIPromptFactory> wwatch =
|
|
do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return wwatch->GetPrompt(nullptr, aIID,
|
|
reinterpret_cast<void**>(result));
|
|
}
|
|
|
|
return NS_NOINTERFACE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpChannelParentListener::nsIChannelEventSink
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpChannelParentListener::AsyncOnChannelRedirect(
|
|
nsIChannel *oldChannel,
|
|
nsIChannel *newChannel,
|
|
uint32_t redirectFlags,
|
|
nsIAsyncVerifyRedirectCallback* callback)
|
|
{
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIParentRedirectingChannel> activeRedirectingChannel =
|
|
do_QueryInterface(mNextListener);
|
|
if (!activeRedirectingChannel) {
|
|
NS_ERROR("Channel got a redirect response, but doesn't implement "
|
|
"nsIParentRedirectingChannel to handle it.");
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
// Register the new channel and obtain id for it
|
|
nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
|
|
do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = registrar->RegisterChannel(newChannel, &mRedirectChannelId);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
LOG(("Registered %p channel under id=%d", newChannel, mRedirectChannelId));
|
|
|
|
return activeRedirectingChannel->StartRedirect(mRedirectChannelId,
|
|
newChannel,
|
|
redirectFlags,
|
|
callback);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpChannelParentListener::nsIRedirectResultListener
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpChannelParentListener::OnRedirectResult(bool succeeded)
|
|
{
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIParentChannel> redirectChannel;
|
|
if (mRedirectChannelId) {
|
|
nsCOMPtr<nsIRedirectChannelRegistrar> registrar =
|
|
do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = registrar->GetParentChannel(mRedirectChannelId,
|
|
getter_AddRefs(redirectChannel));
|
|
if (NS_FAILED(rv) || !redirectChannel) {
|
|
// Redirect might get canceled before we got AsyncOnChannelRedirect
|
|
LOG(("Registered parent channel not found under id=%d", mRedirectChannelId));
|
|
|
|
nsCOMPtr<nsIChannel> newChannel;
|
|
rv = registrar->GetRegisteredChannel(mRedirectChannelId,
|
|
getter_AddRefs(newChannel));
|
|
MOZ_ASSERT(newChannel, "Already registered channel not found");
|
|
|
|
if (NS_SUCCEEDED(rv))
|
|
newChannel->Cancel(NS_BINDING_ABORTED);
|
|
}
|
|
|
|
// Release all previously registered channels, they are no longer need to be
|
|
// kept in the registrar from this moment.
|
|
registrar->DeregisterChannels(mRedirectChannelId);
|
|
|
|
mRedirectChannelId = 0;
|
|
}
|
|
|
|
if (!redirectChannel) {
|
|
succeeded = false;
|
|
}
|
|
|
|
nsCOMPtr<nsIParentRedirectingChannel> activeRedirectingChannel =
|
|
do_QueryInterface(mNextListener);
|
|
MOZ_ASSERT(activeRedirectingChannel,
|
|
"Channel finished a redirect response, but doesn't implement "
|
|
"nsIParentRedirectingChannel to complete it.");
|
|
|
|
if (activeRedirectingChannel) {
|
|
activeRedirectingChannel->CompleteRedirect(succeeded);
|
|
} else {
|
|
succeeded = false;
|
|
}
|
|
|
|
if (succeeded) {
|
|
// Switch to redirect channel and delete the old one.
|
|
nsCOMPtr<nsIParentChannel> parent;
|
|
parent = do_QueryInterface(mNextListener);
|
|
MOZ_ASSERT(parent);
|
|
parent->Delete();
|
|
mNextListener = do_QueryInterface(redirectChannel);
|
|
MOZ_ASSERT(mNextListener);
|
|
redirectChannel->SetParentListener(this);
|
|
} else if (redirectChannel) {
|
|
// Delete the redirect target channel: continue using old channel
|
|
redirectChannel->Delete();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpChannelParentListener::nsINetworkInterceptController
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpChannelParentListener::ShouldPrepareForIntercept(nsIURI* aURI,
|
|
bool aIsNonSubresourceRequest,
|
|
bool* aShouldIntercept)
|
|
{
|
|
*aShouldIntercept = mShouldIntercept;
|
|
return NS_OK;
|
|
}
|
|
|
|
class HeaderVisitor final : public nsIHttpHeaderVisitor
|
|
{
|
|
nsCOMPtr<nsIInterceptedChannel> mChannel;
|
|
~HeaderVisitor()
|
|
{
|
|
}
|
|
public:
|
|
explicit HeaderVisitor(nsIInterceptedChannel* aChannel) : mChannel(aChannel)
|
|
{
|
|
}
|
|
|
|
NS_DECL_ISUPPORTS
|
|
|
|
NS_IMETHOD VisitHeader(const nsACString& aHeader, const nsACString& aValue) override
|
|
{
|
|
mChannel->SynthesizeHeader(aHeader, aValue);
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(HeaderVisitor, nsIHttpHeaderVisitor)
|
|
|
|
class FinishSynthesizedResponse : public Runnable
|
|
{
|
|
nsCOMPtr<nsIInterceptedChannel> mChannel;
|
|
public:
|
|
explicit FinishSynthesizedResponse(nsIInterceptedChannel* aChannel)
|
|
: mChannel(aChannel)
|
|
{
|
|
}
|
|
|
|
NS_IMETHOD Run() override
|
|
{
|
|
// The URL passed as an argument here doesn't matter, since the child will
|
|
// receive a redirection notification as a result of this synthesized response.
|
|
mChannel->FinishSynthesizedResponse(EmptyCString());
|
|
return NS_OK;
|
|
}
|
|
};
|
|
|
|
NS_IMETHODIMP
|
|
HttpChannelParentListener::ChannelIntercepted(nsIInterceptedChannel* aChannel)
|
|
{
|
|
if (mShouldSuspendIntercept) {
|
|
mInterceptedChannel = aChannel;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsAutoCString statusText;
|
|
mSynthesizedResponseHead->StatusText(statusText);
|
|
aChannel->SynthesizeStatus(mSynthesizedResponseHead->Status(), statusText);
|
|
nsCOMPtr<nsIHttpHeaderVisitor> visitor = new HeaderVisitor(aChannel);
|
|
mSynthesizedResponseHead->VisitHeaders(visitor,
|
|
nsHttpHeaderArray::eFilterResponse);
|
|
|
|
nsCOMPtr<nsIRunnable> event = new FinishSynthesizedResponse(aChannel);
|
|
NS_DispatchToCurrentThread(event);
|
|
|
|
mSynthesizedResponseHead = nullptr;
|
|
|
|
MOZ_ASSERT(mNextListener);
|
|
RefPtr<HttpChannelParent> channel = do_QueryObject(mNextListener);
|
|
MOZ_ASSERT(channel);
|
|
channel->ResponseSynthesized();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
nsresult
|
|
HttpChannelParentListener::SuspendForDiversion()
|
|
{
|
|
if (NS_WARN_IF(mSuspendedForDiversion)) {
|
|
MOZ_ASSERT(!mSuspendedForDiversion, "Cannot SuspendForDiversion twice!");
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
// While this is set, no OnStart/OnData/OnStop callbacks should be forwarded
|
|
// to mNextListener.
|
|
mSuspendedForDiversion = true;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
HttpChannelParentListener::ResumeForDiversion()
|
|
{
|
|
MOZ_RELEASE_ASSERT(mSuspendedForDiversion, "Must already be suspended!");
|
|
|
|
// Allow OnStart/OnData/OnStop callbacks to be forwarded to mNextListener.
|
|
mSuspendedForDiversion = false;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
HttpChannelParentListener::DivertTo(nsIStreamListener* aListener)
|
|
{
|
|
MOZ_ASSERT(aListener);
|
|
MOZ_RELEASE_ASSERT(mSuspendedForDiversion, "Must already be suspended!");
|
|
|
|
mNextListener = aListener;
|
|
|
|
return ResumeForDiversion();
|
|
}
|
|
|
|
void
|
|
HttpChannelParentListener::SetupInterception(const nsHttpResponseHead& aResponseHead)
|
|
{
|
|
mSynthesizedResponseHead = new nsHttpResponseHead(aResponseHead);
|
|
mShouldIntercept = true;
|
|
}
|
|
|
|
void
|
|
HttpChannelParentListener::SetupInterceptionAfterRedirect(bool aShouldIntercept)
|
|
{
|
|
mShouldIntercept = aShouldIntercept;
|
|
if (mShouldIntercept) {
|
|
// When an interception occurs, this channel should suspend all further activity.
|
|
// It will be torn down and recreated if necessary.
|
|
mShouldSuspendIntercept = true;
|
|
}
|
|
}
|
|
|
|
void
|
|
HttpChannelParentListener::ClearInterceptedChannel()
|
|
{
|
|
if (mInterceptedChannel) {
|
|
mInterceptedChannel->Cancel(NS_ERROR_INTERCEPTION_FAILED);
|
|
mInterceptedChannel = nullptr;
|
|
}
|
|
}
|
|
|
|
} // namespace net
|
|
} // namespace mozilla
|