mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-25 03:05:34 +00:00
635fc0e1ae
After data delivery for a request has been retargeted, there's no reliable way to get the appropriate event target to re-dispatch data events after asynchronous processing. While it's technically possible to retrieve the current thread from OnDataAvailable callbacks and re-use that for later dispatch, that approach has some issues: 1) It's not currently possible to reliably map the current thread to the thread pool that owns it. That means that if data delivery is being targetted to a thread pool, attempts to redispatch events to the previous delivery thread might lead to long delays when one thread in a pool is blocked. 2) If a filter wishes to dispatch data events to the wrapped listeners before it's recieved any data (as extensions StreamFilters sometimes do), there's no way to determine the proper event target without waiting for initial data to be received. Simply returning the correct event target from the request solves both of these problems. MozReview-Commit-ID: CJxq7O4399R --HG-- extra : rebase_source : db2f659ecad16daafdbcc108d7b1a51ea1af31f9
1015 lines
27 KiB
C++
1015 lines
27 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set sw=2 sts=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/. */
|
|
|
|
#include "nsBaseChannel.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsURLHelper.h"
|
|
#include "nsNetCID.h"
|
|
#include "nsMimeTypes.h"
|
|
#include "nsIContentSniffer.h"
|
|
#include "nsIScriptSecurityManager.h"
|
|
#include "nsMimeTypes.h"
|
|
#include "nsIHttpChannel.h"
|
|
#include "nsIChannelEventSink.h"
|
|
#include "nsIStreamConverterService.h"
|
|
#include "nsChannelClassifier.h"
|
|
#include "nsAsyncRedirectVerifyHelper.h"
|
|
#include "nsProxyRelease.h"
|
|
#include "nsXULAppAPI.h"
|
|
#include "nsContentSecurityManager.h"
|
|
#include "LoadInfo.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#include "nsRedirectHistoryEntry.h"
|
|
|
|
// This class is used to suspend a request across a function scope.
|
|
class ScopedRequestSuspender {
|
|
public:
|
|
explicit ScopedRequestSuspender(nsIRequest *request)
|
|
: mRequest(request) {
|
|
if (mRequest && NS_FAILED(mRequest->Suspend())) {
|
|
NS_WARNING("Couldn't suspend pump");
|
|
mRequest = nullptr;
|
|
}
|
|
}
|
|
~ScopedRequestSuspender() {
|
|
if (mRequest)
|
|
mRequest->Resume();
|
|
}
|
|
private:
|
|
nsIRequest *mRequest;
|
|
};
|
|
|
|
// Used to suspend data events from mRequest within a function scope. This is
|
|
// usually needed when a function makes callbacks that could process events.
|
|
#define SUSPEND_PUMP_FOR_SCOPE() \
|
|
ScopedRequestSuspender pump_suspender__(mRequest)
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nsBaseChannel
|
|
|
|
nsBaseChannel::nsBaseChannel()
|
|
: NeckoTargetHolder(nullptr)
|
|
, mPumpingData(false)
|
|
, mLoadFlags(LOAD_NORMAL)
|
|
, mQueriedProgressSink(true)
|
|
, mSynthProgressEvents(false)
|
|
, mAllowThreadRetargeting(true)
|
|
, mWaitingOnAsyncRedirect(false)
|
|
, mOpenRedirectChannel(false)
|
|
, mStatus(NS_OK)
|
|
, mContentDispositionHint(UINT32_MAX)
|
|
, mContentLength(-1)
|
|
, mWasOpened(false)
|
|
{
|
|
mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
|
|
}
|
|
|
|
nsBaseChannel::~nsBaseChannel()
|
|
{
|
|
NS_ReleaseOnMainThreadSystemGroup(
|
|
"nsBaseChannel::mLoadInfo", mLoadInfo.forget());
|
|
}
|
|
|
|
nsresult
|
|
nsBaseChannel::Redirect(nsIChannel *newChannel, uint32_t redirectFlags,
|
|
bool openNewChannel)
|
|
{
|
|
SUSPEND_PUMP_FOR_SCOPE();
|
|
|
|
// Transfer properties
|
|
|
|
newChannel->SetLoadGroup(mLoadGroup);
|
|
newChannel->SetNotificationCallbacks(mCallbacks);
|
|
newChannel->SetLoadFlags(mLoadFlags | LOAD_REPLACE);
|
|
|
|
// make a copy of the loadinfo, append to the redirectchain
|
|
// and set it on the new channel
|
|
if (mLoadInfo) {
|
|
nsSecurityFlags secFlags = mLoadInfo->GetSecurityFlags() &
|
|
~nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
|
|
nsCOMPtr<nsILoadInfo> newLoadInfo =
|
|
static_cast<mozilla::LoadInfo*>(mLoadInfo.get())->CloneWithNewSecFlags(secFlags);
|
|
|
|
nsCOMPtr<nsIPrincipal> uriPrincipal;
|
|
nsIScriptSecurityManager *sm = nsContentUtils::GetSecurityManager();
|
|
sm->GetChannelURIPrincipal(this, getter_AddRefs(uriPrincipal));
|
|
bool isInternalRedirect =
|
|
(redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
|
|
nsIChannelEventSink::REDIRECT_STS_UPGRADE));
|
|
|
|
// nsBaseChannel hst no thing to do with HttpBaseChannel, we would not care
|
|
// about referrer and remote address in this case
|
|
nsCOMPtr<nsIRedirectHistoryEntry> entry =
|
|
new nsRedirectHistoryEntry(uriPrincipal, nullptr, EmptyCString());
|
|
|
|
newLoadInfo->AppendRedirectHistoryEntry(entry, isInternalRedirect);
|
|
|
|
// Ensure the channel's loadInfo's result principal URI so that it's
|
|
// either non-null or updated to the redirect target URI.
|
|
// We must do this because in case the loadInfo's result principal URI
|
|
// is null, it would be taken from OriginalURI of the channel. But we
|
|
// overwrite it with the whole redirect chain first URI before opening
|
|
// the target channel, hence the information would be lost.
|
|
// If the protocol handler that created the channel wants to use
|
|
// the originalURI of the channel as the principal URI, it has left
|
|
// the result principal URI on the load info null.
|
|
nsCOMPtr<nsIURI> resultPrincipalURI;
|
|
|
|
nsCOMPtr<nsILoadInfo> existingLoadInfo = newChannel->GetLoadInfo();
|
|
if (existingLoadInfo) {
|
|
existingLoadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI));
|
|
}
|
|
if (!resultPrincipalURI) {
|
|
newChannel->GetOriginalURI(getter_AddRefs(resultPrincipalURI));
|
|
}
|
|
|
|
newLoadInfo->SetResultPrincipalURI(resultPrincipalURI);
|
|
|
|
newChannel->SetLoadInfo(newLoadInfo);
|
|
}
|
|
else {
|
|
// the newChannel was created with a dummy loadInfo, we should clear
|
|
// it in case the original channel does not have a loadInfo
|
|
newChannel->SetLoadInfo(nullptr);
|
|
}
|
|
|
|
// Preserve the privacy bit if it has been overridden
|
|
if (mPrivateBrowsingOverriden) {
|
|
nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel =
|
|
do_QueryInterface(newChannel);
|
|
if (newPBChannel) {
|
|
newPBChannel->SetPrivate(mPrivateBrowsing);
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIWritablePropertyBag> bag = ::do_QueryInterface(newChannel);
|
|
if (bag) {
|
|
for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) {
|
|
bag->SetProperty(iter.Key(), iter.UserData());
|
|
}
|
|
}
|
|
|
|
// Notify consumer, giving chance to cancel redirect.
|
|
|
|
RefPtr<nsAsyncRedirectVerifyHelper> redirectCallbackHelper =
|
|
new nsAsyncRedirectVerifyHelper();
|
|
|
|
bool checkRedirectSynchronously = !openNewChannel;
|
|
nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
|
|
|
|
mRedirectChannel = newChannel;
|
|
mRedirectFlags = redirectFlags;
|
|
mOpenRedirectChannel = openNewChannel;
|
|
nsresult rv = redirectCallbackHelper->Init(this, newChannel, redirectFlags,
|
|
target, checkRedirectSynchronously);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
if (checkRedirectSynchronously && NS_FAILED(mStatus))
|
|
return mStatus;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsBaseChannel::ContinueRedirect()
|
|
{
|
|
// Make sure to do this _after_ making all the OnChannelRedirect calls
|
|
mRedirectChannel->SetOriginalURI(OriginalURI());
|
|
|
|
// If we fail to open the new channel, then we want to leave this channel
|
|
// unaffected, so we defer tearing down our channel until we have succeeded
|
|
// with the redirect.
|
|
|
|
if (mOpenRedirectChannel) {
|
|
nsresult rv = NS_OK;
|
|
if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
|
|
MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
|
|
rv = mRedirectChannel->AsyncOpen2(mListener);
|
|
}
|
|
else {
|
|
rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
|
|
}
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
mRedirectChannel = nullptr;
|
|
|
|
// close down this channel
|
|
Cancel(NS_BINDING_REDIRECTED);
|
|
ChannelDone();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
nsBaseChannel::HasContentTypeHint() const
|
|
{
|
|
NS_ASSERTION(!Pending(), "HasContentTypeHint called too late");
|
|
return !mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE);
|
|
}
|
|
|
|
nsresult
|
|
nsBaseChannel::PushStreamConverter(const char *fromType,
|
|
const char *toType,
|
|
bool invalidatesContentLength,
|
|
nsIStreamListener **result)
|
|
{
|
|
NS_ASSERTION(mListener, "no listener");
|
|
|
|
nsresult rv;
|
|
nsCOMPtr<nsIStreamConverterService> scs =
|
|
do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
nsCOMPtr<nsIStreamListener> converter;
|
|
rv = scs->AsyncConvertData(fromType, toType, mListener, mListenerContext,
|
|
getter_AddRefs(converter));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mListener = converter;
|
|
if (invalidatesContentLength)
|
|
mContentLength = -1;
|
|
if (result) {
|
|
*result = nullptr;
|
|
converter.swap(*result);
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsBaseChannel::BeginPumpingData()
|
|
{
|
|
nsresult rv;
|
|
|
|
rv = BeginAsyncRead(this, getter_AddRefs(mRequest));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mPumpingData = true;
|
|
return NS_OK;
|
|
}
|
|
if (rv != NS_ERROR_NOT_IMPLEMENTED) {
|
|
return rv;
|
|
}
|
|
|
|
nsCOMPtr<nsIInputStream> stream;
|
|
nsCOMPtr<nsIChannel> channel;
|
|
rv = OpenContentStream(true, getter_AddRefs(stream),
|
|
getter_AddRefs(channel));
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
NS_ASSERTION(!stream || !channel, "Got both a channel and a stream?");
|
|
|
|
if (channel) {
|
|
nsCOMPtr<nsIRunnable> runnable = new RedirectRunnable(this, channel);
|
|
rv = Dispatch(runnable.forget());
|
|
if (NS_SUCCEEDED(rv))
|
|
mWaitingOnAsyncRedirect = true;
|
|
return rv;
|
|
}
|
|
|
|
// By assigning mPump, we flag this channel as pending (see Pending). It's
|
|
// important that the pending flag is set when we call into the stream (the
|
|
// call to AsyncRead results in the stream's AsyncWait method being called)
|
|
// and especially when we call into the loadgroup. Our caller takes care to
|
|
// release mPump if we return an error.
|
|
|
|
nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
|
|
rv = nsInputStreamPump::Create(getter_AddRefs(mPump), stream, 0, 0, true,
|
|
target);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mPumpingData = true;
|
|
mRequest = mPump;
|
|
rv = mPump->AsyncRead(this, nullptr);
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
nsBaseChannel::HandleAsyncRedirect(nsIChannel* newChannel)
|
|
{
|
|
NS_ASSERTION(!mPumpingData, "Shouldn't have gotten here");
|
|
|
|
nsresult rv = mStatus;
|
|
if (NS_SUCCEEDED(mStatus)) {
|
|
rv = Redirect(newChannel,
|
|
nsIChannelEventSink::REDIRECT_TEMPORARY,
|
|
true);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
// OnRedirectVerifyCallback will be called asynchronously
|
|
return;
|
|
}
|
|
}
|
|
|
|
ContinueHandleAsyncRedirect(rv);
|
|
}
|
|
|
|
void
|
|
nsBaseChannel::ContinueHandleAsyncRedirect(nsresult result)
|
|
{
|
|
mWaitingOnAsyncRedirect = false;
|
|
|
|
if (NS_FAILED(result))
|
|
Cancel(result);
|
|
|
|
if (NS_FAILED(result) && mListener) {
|
|
// Notify our consumer ourselves
|
|
mListener->OnStartRequest(this, mListenerContext);
|
|
mListener->OnStopRequest(this, mListenerContext, mStatus);
|
|
ChannelDone();
|
|
}
|
|
|
|
if (mLoadGroup)
|
|
mLoadGroup->RemoveRequest(this, nullptr, mStatus);
|
|
|
|
// Drop notification callbacks to prevent cycles.
|
|
mCallbacks = nullptr;
|
|
CallbacksChanged();
|
|
}
|
|
|
|
void
|
|
nsBaseChannel::ClassifyURI()
|
|
{
|
|
// For channels created in the child process, delegate to the parent to
|
|
// classify URIs.
|
|
if (!XRE_IsParentProcess()) {
|
|
return;
|
|
}
|
|
|
|
if (mLoadFlags & LOAD_CLASSIFY_URI) {
|
|
RefPtr<nsChannelClassifier> classifier = new nsChannelClassifier(this);
|
|
if (classifier) {
|
|
classifier->Start();
|
|
} else {
|
|
Cancel(NS_ERROR_OUT_OF_MEMORY);
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nsBaseChannel::nsISupports
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED(nsBaseChannel,
|
|
nsHashPropertyBag,
|
|
nsIRequest,
|
|
nsIChannel,
|
|
nsIThreadRetargetableRequest,
|
|
nsIInterfaceRequestor,
|
|
nsITransportEventSink,
|
|
nsIRequestObserver,
|
|
nsIStreamListener,
|
|
nsIThreadRetargetableStreamListener,
|
|
nsIAsyncVerifyRedirectCallback,
|
|
nsIPrivateBrowsingChannel)
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nsBaseChannel::nsIRequest
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetName(nsACString &result)
|
|
{
|
|
if (!mURI) {
|
|
result.Truncate();
|
|
return NS_OK;
|
|
}
|
|
return mURI->GetSpec(result);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::IsPending(bool *result)
|
|
{
|
|
*result = Pending();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetStatus(nsresult *status)
|
|
{
|
|
if (mRequest && NS_SUCCEEDED(mStatus)) {
|
|
mRequest->GetStatus(status);
|
|
} else {
|
|
*status = mStatus;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::Cancel(nsresult status)
|
|
{
|
|
// Ignore redundant cancelation
|
|
if (NS_FAILED(mStatus))
|
|
return NS_OK;
|
|
|
|
mStatus = status;
|
|
|
|
if (mRequest)
|
|
mRequest->Cancel(status);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::Suspend()
|
|
{
|
|
NS_ENSURE_TRUE(mPumpingData, NS_ERROR_NOT_INITIALIZED);
|
|
NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_IMPLEMENTED);
|
|
return mRequest->Suspend();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::Resume()
|
|
{
|
|
NS_ENSURE_TRUE(mPumpingData, NS_ERROR_NOT_INITIALIZED);
|
|
NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_IMPLEMENTED);
|
|
return mRequest->Resume();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
|
|
{
|
|
*aLoadFlags = mLoadFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
|
|
{
|
|
mLoadFlags = aLoadFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
|
|
{
|
|
NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
|
|
{
|
|
if (!CanSetLoadGroup(aLoadGroup)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mLoadGroup = aLoadGroup;
|
|
CallbacksChanged();
|
|
UpdatePrivateBrowsing();
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nsBaseChannel::nsIChannel
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetOriginalURI(nsIURI **aURI)
|
|
{
|
|
*aURI = OriginalURI();
|
|
NS_ADDREF(*aURI);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::SetOriginalURI(nsIURI *aURI)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aURI);
|
|
mOriginalURI = aURI;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetURI(nsIURI **aURI)
|
|
{
|
|
NS_IF_ADDREF(*aURI = mURI);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetOwner(nsISupports **aOwner)
|
|
{
|
|
NS_IF_ADDREF(*aOwner = mOwner);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::SetOwner(nsISupports *aOwner)
|
|
{
|
|
mOwner = aOwner;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::SetLoadInfo(nsILoadInfo* aLoadInfo)
|
|
{
|
|
mLoadInfo = aLoadInfo;
|
|
|
|
// Need to update |mNeckoTarget| when load info has changed.
|
|
SetupNeckoTarget();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetLoadInfo(nsILoadInfo** aLoadInfo)
|
|
{
|
|
NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetIsDocument(bool *aIsDocument)
|
|
{
|
|
return NS_GetIsDocumentChannel(this, aIsDocument);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks)
|
|
{
|
|
NS_IF_ADDREF(*aCallbacks = mCallbacks);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
|
|
{
|
|
if (!CanSetCallbacks(aCallbacks)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mCallbacks = aCallbacks;
|
|
CallbacksChanged();
|
|
UpdatePrivateBrowsing();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
|
|
{
|
|
NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetContentType(nsACString &aContentType)
|
|
{
|
|
aContentType = mContentType;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::SetContentType(const nsACString &aContentType)
|
|
{
|
|
// mContentCharset is unchanged if not parsed
|
|
bool dummy;
|
|
net_ParseContentType(aContentType, mContentType, mContentCharset, &dummy);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetContentCharset(nsACString &aContentCharset)
|
|
{
|
|
aContentCharset = mContentCharset;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::SetContentCharset(const nsACString &aContentCharset)
|
|
{
|
|
mContentCharset = aContentCharset;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetContentDisposition(uint32_t *aContentDisposition)
|
|
{
|
|
// preserve old behavior, fail unless explicitly set.
|
|
if (mContentDispositionHint == UINT32_MAX) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
*aContentDisposition = mContentDispositionHint;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::SetContentDisposition(uint32_t aContentDisposition)
|
|
{
|
|
mContentDispositionHint = aContentDisposition;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename)
|
|
{
|
|
if (!mContentDispositionFilename) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
aContentDispositionFilename = *mContentDispositionFilename;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename)
|
|
{
|
|
mContentDispositionFilename = new nsString(aContentDispositionFilename);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader)
|
|
{
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetContentLength(int64_t *aContentLength)
|
|
{
|
|
*aContentLength = mContentLength;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::SetContentLength(int64_t aContentLength)
|
|
{
|
|
mContentLength = aContentLength;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::Open(nsIInputStream **result)
|
|
{
|
|
NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
|
|
NS_ENSURE_TRUE(!mPumpingData, NS_ERROR_IN_PROGRESS);
|
|
NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_IN_PROGRESS);
|
|
|
|
nsCOMPtr<nsIChannel> chan;
|
|
nsresult rv = OpenContentStream(false, result, getter_AddRefs(chan));
|
|
NS_ASSERTION(!chan || !*result, "Got both a channel and a stream?");
|
|
if (NS_SUCCEEDED(rv) && chan) {
|
|
rv = Redirect(chan, nsIChannelEventSink::REDIRECT_INTERNAL, false);
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
rv = chan->Open(result);
|
|
} else if (rv == NS_ERROR_NOT_IMPLEMENTED)
|
|
return NS_ImplementChannelOpen(this, result);
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mWasOpened = true;
|
|
ClassifyURI();
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::Open2(nsIInputStream** aStream)
|
|
{
|
|
nsCOMPtr<nsIStreamListener> listener;
|
|
nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
return Open(aStream);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *ctxt)
|
|
{
|
|
MOZ_ASSERT(!mLoadInfo ||
|
|
mLoadInfo->GetSecurityMode() == 0 ||
|
|
mLoadInfo->GetInitialSecurityCheckDone() ||
|
|
(mLoadInfo->GetSecurityMode() == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
|
|
nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
|
|
"security flags in loadInfo but asyncOpen2() not called");
|
|
|
|
NS_ENSURE_TRUE(mURI, NS_ERROR_NOT_INITIALIZED);
|
|
NS_ENSURE_TRUE(!mPumpingData, NS_ERROR_IN_PROGRESS);
|
|
NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
|
|
NS_ENSURE_ARG(listener);
|
|
|
|
SetupNeckoTarget();
|
|
|
|
// Skip checking for chrome:// sub-resources.
|
|
nsAutoCString scheme;
|
|
mURI->GetScheme(scheme);
|
|
if (!scheme.EqualsLiteral("file")) {
|
|
NS_CompareLoadInfoAndLoadContext(this);
|
|
}
|
|
|
|
// Ensure that this is an allowed port before proceeding.
|
|
nsresult rv = NS_CheckPortSafety(mURI);
|
|
if (NS_FAILED(rv)) {
|
|
mCallbacks = nullptr;
|
|
return rv;
|
|
}
|
|
|
|
// Store the listener and context early so that OpenContentStream and the
|
|
// stream's AsyncWait method (called by AsyncRead) can have access to them
|
|
// via PushStreamConverter and the StreamListener methods. However, since
|
|
// this typically introduces a reference cycle between this and the listener,
|
|
// we need to be sure to break the reference if this method does not succeed.
|
|
mListener = listener;
|
|
mListenerContext = ctxt;
|
|
|
|
// This method assigns mPump as a side-effect. We need to clear mPump if
|
|
// this method fails.
|
|
rv = BeginPumpingData();
|
|
if (NS_FAILED(rv)) {
|
|
mPump = nullptr;
|
|
mRequest = nullptr;
|
|
mPumpingData = false;
|
|
ChannelDone();
|
|
mCallbacks = nullptr;
|
|
return rv;
|
|
}
|
|
|
|
// At this point, we are going to return success no matter what.
|
|
|
|
mWasOpened = true;
|
|
|
|
SUSPEND_PUMP_FOR_SCOPE();
|
|
|
|
if (mLoadGroup)
|
|
mLoadGroup->AddRequest(this, nullptr);
|
|
|
|
ClassifyURI();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::AsyncOpen2(nsIStreamListener *aListener)
|
|
{
|
|
nsCOMPtr<nsIStreamListener> listener = aListener;
|
|
nsresult rv = nsContentSecurityManager::doContentSecurityCheck(this, listener);
|
|
if (NS_FAILED(rv)) {
|
|
mCallbacks = nullptr;
|
|
return rv;
|
|
}
|
|
return AsyncOpen(listener, nullptr);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nsBaseChannel::nsITransportEventSink
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::OnTransportStatus(nsITransport *transport, nsresult status,
|
|
int64_t progress, int64_t progressMax)
|
|
{
|
|
// In some cases, we may wish to suppress transport-layer status events.
|
|
|
|
if (!mPumpingData || NS_FAILED(mStatus)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
SUSPEND_PUMP_FOR_SCOPE();
|
|
|
|
// Lazily fetch mProgressSink
|
|
if (!mProgressSink) {
|
|
if (mQueriedProgressSink) {
|
|
return NS_OK;
|
|
}
|
|
GetCallback(mProgressSink);
|
|
mQueriedProgressSink = true;
|
|
if (!mProgressSink) {
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
if (!HasLoadFlag(LOAD_BACKGROUND)) {
|
|
nsAutoString statusArg;
|
|
if (GetStatusArg(status, statusArg)) {
|
|
mProgressSink->OnStatus(this, mListenerContext, status, statusArg.get());
|
|
}
|
|
}
|
|
|
|
if (progress) {
|
|
mProgressSink->OnProgress(this, mListenerContext, progress, progressMax);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nsBaseChannel::nsIInterfaceRequestor
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetInterface(const nsIID &iid, void **result)
|
|
{
|
|
NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, iid, result);
|
|
return *result ? NS_OK : NS_ERROR_NO_INTERFACE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nsBaseChannel::nsIRequestObserver
|
|
|
|
static void
|
|
CallTypeSniffers(void *aClosure, const uint8_t *aData, uint32_t aCount)
|
|
{
|
|
nsIChannel *chan = static_cast<nsIChannel*>(aClosure);
|
|
|
|
nsAutoCString newType;
|
|
NS_SniffContent(NS_CONTENT_SNIFFER_CATEGORY, chan, aData, aCount, newType);
|
|
if (!newType.IsEmpty()) {
|
|
chan->SetContentType(newType);
|
|
}
|
|
}
|
|
|
|
static void
|
|
CallUnknownTypeSniffer(void *aClosure, const uint8_t *aData, uint32_t aCount)
|
|
{
|
|
nsIChannel *chan = static_cast<nsIChannel*>(aClosure);
|
|
|
|
nsCOMPtr<nsIContentSniffer> sniffer =
|
|
do_CreateInstance(NS_GENERIC_CONTENT_SNIFFER);
|
|
if (!sniffer)
|
|
return;
|
|
|
|
nsAutoCString detected;
|
|
nsresult rv = sniffer->GetMIMETypeFromContent(chan, aData, aCount, detected);
|
|
if (NS_SUCCEEDED(rv))
|
|
chan->SetContentType(detected);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
|
|
{
|
|
MOZ_ASSERT_IF(mRequest, request == mRequest);
|
|
|
|
if (mPump) {
|
|
// If our content type is unknown, use the content type
|
|
// sniffer. If the sniffer is not available for some reason, then we just keep
|
|
// going as-is.
|
|
if (NS_SUCCEEDED(mStatus) &&
|
|
mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
|
|
mPump->PeekStream(CallUnknownTypeSniffer, static_cast<nsIChannel*>(this));
|
|
}
|
|
|
|
// Now, the general type sniffers. Skip this if we have none.
|
|
if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS)
|
|
mPump->PeekStream(CallTypeSniffers, static_cast<nsIChannel*>(this));
|
|
}
|
|
|
|
SUSPEND_PUMP_FOR_SCOPE();
|
|
|
|
if (mListener) // null in case of redirect
|
|
return mListener->OnStartRequest(this, mListenerContext);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
|
|
nsresult status)
|
|
{
|
|
// If both mStatus and status are failure codes, we keep mStatus as-is since
|
|
// that is consistent with our GetStatus and Cancel methods.
|
|
if (NS_SUCCEEDED(mStatus))
|
|
mStatus = status;
|
|
|
|
// Cause Pending to return false.
|
|
mPump = nullptr;
|
|
mRequest = nullptr;
|
|
mPumpingData = false;
|
|
|
|
if (mListener) // null in case of redirect
|
|
mListener->OnStopRequest(this, mListenerContext, mStatus);
|
|
ChannelDone();
|
|
|
|
// No need to suspend pump in this scope since we will not be receiving
|
|
// any more events from it.
|
|
|
|
if (mLoadGroup)
|
|
mLoadGroup->RemoveRequest(this, nullptr, mStatus);
|
|
|
|
// Drop notification callbacks to prevent cycles.
|
|
mCallbacks = nullptr;
|
|
CallbacksChanged();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nsBaseChannel::nsIStreamListener
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
|
|
nsIInputStream *stream, uint64_t offset,
|
|
uint32_t count)
|
|
{
|
|
SUSPEND_PUMP_FOR_SCOPE();
|
|
|
|
nsresult rv = mListener->OnDataAvailable(this, mListenerContext, stream,
|
|
offset, count);
|
|
if (mSynthProgressEvents && NS_SUCCEEDED(rv)) {
|
|
int64_t prog = offset + count;
|
|
if (NS_IsMainThread()) {
|
|
OnTransportStatus(nullptr, NS_NET_STATUS_READING, prog, mContentLength);
|
|
} else {
|
|
class OnTransportStatusAsyncEvent : public mozilla::Runnable
|
|
{
|
|
RefPtr<nsBaseChannel> mChannel;
|
|
int64_t mProgress;
|
|
int64_t mContentLength;
|
|
public:
|
|
OnTransportStatusAsyncEvent(nsBaseChannel* aChannel,
|
|
int64_t aProgress,
|
|
int64_t aContentLength)
|
|
: mozilla::Runnable("OnTransportStatusAsyncEvent")
|
|
, mChannel(aChannel)
|
|
, mProgress(aProgress)
|
|
, mContentLength(aContentLength)
|
|
{ }
|
|
|
|
NS_IMETHOD Run() override
|
|
{
|
|
return mChannel->OnTransportStatus(nullptr, NS_NET_STATUS_READING,
|
|
mProgress, mContentLength);
|
|
}
|
|
};
|
|
|
|
nsCOMPtr<nsIRunnable> runnable =
|
|
new OnTransportStatusAsyncEvent(this, prog, mContentLength);
|
|
Dispatch(runnable.forget());
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::OnRedirectVerifyCallback(nsresult result)
|
|
{
|
|
if (NS_SUCCEEDED(result))
|
|
result = ContinueRedirect();
|
|
|
|
if (NS_FAILED(result) && !mWaitingOnAsyncRedirect) {
|
|
if (NS_SUCCEEDED(mStatus))
|
|
mStatus = result;
|
|
return NS_OK;
|
|
}
|
|
|
|
if (mWaitingOnAsyncRedirect)
|
|
ContinueHandleAsyncRedirect(result);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::RetargetDeliveryTo(nsIEventTarget* aEventTarget)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
nsCOMPtr<nsIThreadRetargetableRequest> req;
|
|
if (mAllowThreadRetargeting) {
|
|
req = do_QueryInterface(mRequest);
|
|
}
|
|
|
|
NS_ENSURE_TRUE(req, NS_ERROR_NOT_IMPLEMENTED);
|
|
|
|
return req->RetargetDeliveryTo(aEventTarget);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::GetDeliveryTarget(nsIEventTarget** aEventTarget)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
NS_ENSURE_TRUE(mRequest, NS_ERROR_NOT_INITIALIZED);
|
|
|
|
nsCOMPtr<nsIThreadRetargetableRequest> req;
|
|
req = do_QueryInterface(mRequest);
|
|
|
|
NS_ENSURE_TRUE(req, NS_ERROR_NOT_IMPLEMENTED);
|
|
return req->GetDeliveryTarget(aEventTarget);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseChannel::CheckListenerChain()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!mAllowThreadRetargeting) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
|
|
do_QueryInterface(mListener);
|
|
if (!listener) {
|
|
return NS_ERROR_NO_INTERFACE;
|
|
}
|
|
|
|
return listener->CheckListenerChain();
|
|
}
|
|
|
|
void
|
|
nsBaseChannel::SetupNeckoTarget()
|
|
{
|
|
mNeckoTarget =
|
|
nsContentUtils::GetEventTargetByLoadInfo(mLoadInfo, TaskCategory::Other);
|
|
}
|