зеркало из https://github.com/mozilla/gecko-dev.git
4846 строки
148 KiB
C++
4846 строки
148 KiB
C++
/* -*- 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 "mozilla/net/HttpBaseChannel.h"
|
|
|
|
#include <algorithm>
|
|
#include <utility>
|
|
|
|
#include "HttpBaseChannel.h"
|
|
#include "HttpLog.h"
|
|
#include "LoadInfo.h"
|
|
#include "mozIThirdPartyUtil.h"
|
|
#include "mozilla/AntiTrackingUtils.h"
|
|
#include "mozilla/BasePrincipal.h"
|
|
#include "mozilla/BinarySearch.h"
|
|
#include "mozilla/ConsoleReportCollector.h"
|
|
#include "mozilla/DebugOnly.h"
|
|
#include "mozilla/InputStreamLengthHelper.h"
|
|
#include "mozilla/NullPrincipal.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/StaticPrefs_browser.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "mozilla/Tokenizer.h"
|
|
#include "mozilla/dom/BrowsingContext.h"
|
|
#include "mozilla/dom/CanonicalBrowsingContext.h"
|
|
#include "mozilla/dom/Performance.h"
|
|
#include "mozilla/dom/PerformanceStorage.h"
|
|
#include "mozilla/dom/WindowGlobalParent.h"
|
|
#include "mozilla/net/PartiallySeekableInputStream.h"
|
|
#include "mozilla/net/UrlClassifierCommon.h"
|
|
#include "mozilla/net/UrlClassifierFeatureFactory.h"
|
|
#include "nsCRT.h"
|
|
#include "nsContentSecurityManager.h"
|
|
#include "nsContentSecurityUtils.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsEscape.h"
|
|
#include "nsGlobalWindowOuter.h"
|
|
#include "nsHttpChannel.h"
|
|
#include "nsHttpHandler.h"
|
|
#include "nsIApplicationCacheChannel.h"
|
|
#include "nsICacheInfoChannel.h"
|
|
#include "nsICachingChannel.h"
|
|
#include "nsIChannelEventSink.h"
|
|
#include "nsIConsoleService.h"
|
|
#include "nsIContentPolicy.h"
|
|
#include "nsICookieService.h"
|
|
#include "nsIDOMWindowUtils.h"
|
|
#include "nsIDocShell.h"
|
|
#include "nsIEncodedChannel.h"
|
|
#include "nsIHttpHeaderVisitor.h"
|
|
#include "nsILoadGroupChild.h"
|
|
#include "nsIMIMEInputStream.h"
|
|
#include "nsIMutableArray.h"
|
|
#include "nsINetworkInterceptController.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsIPrincipal.h"
|
|
#include "nsIProtocolProxyService.h"
|
|
#include "nsIScriptError.h"
|
|
#include "nsIScriptSecurityManager.h"
|
|
#include "nsISecurityConsoleMessage.h"
|
|
#include "nsISeekableStream.h"
|
|
#include "nsIStorageStream.h"
|
|
#include "nsIStreamConverterService.h"
|
|
#include "nsITimedChannel.h"
|
|
#include "nsITransportSecurityInfo.h"
|
|
#include "nsIURIMutator.h"
|
|
#include "nsMimeTypes.h"
|
|
#include "nsNetCID.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsPIDOMWindow.h"
|
|
#include "nsProxyRelease.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsRedirectHistoryEntry.h"
|
|
#include "nsServerTiming.h"
|
|
#include "nsStreamListenerWrapper.h"
|
|
#include "nsStreamUtils.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsURLHelper.h"
|
|
#include "mozilla/RemoteLazyInputStreamChild.h"
|
|
#include "mozilla/RemoteLazyInputStreamUtils.h"
|
|
|
|
namespace mozilla {
|
|
namespace net {
|
|
|
|
static bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader) {
|
|
// IMPORTANT: keep this list ASCII-code sorted
|
|
static nsHttpAtom const* blackList[] = {&nsHttp::Accept,
|
|
&nsHttp::Accept_Encoding,
|
|
&nsHttp::Accept_Language,
|
|
&nsHttp::Authentication,
|
|
&nsHttp::Authorization,
|
|
&nsHttp::Connection,
|
|
&nsHttp::Content_Length,
|
|
&nsHttp::Cookie,
|
|
&nsHttp::Host,
|
|
&nsHttp::If,
|
|
&nsHttp::If_Match,
|
|
&nsHttp::If_Modified_Since,
|
|
&nsHttp::If_None_Match,
|
|
&nsHttp::If_None_Match_Any,
|
|
&nsHttp::If_Range,
|
|
&nsHttp::If_Unmodified_Since,
|
|
&nsHttp::Proxy_Authenticate,
|
|
&nsHttp::Proxy_Authorization,
|
|
&nsHttp::Range,
|
|
&nsHttp::TE,
|
|
&nsHttp::Transfer_Encoding,
|
|
&nsHttp::Upgrade,
|
|
&nsHttp::User_Agent,
|
|
&nsHttp::WWW_Authenticate};
|
|
|
|
class HttpAtomComparator {
|
|
nsHttpAtom const& mTarget;
|
|
|
|
public:
|
|
explicit HttpAtomComparator(nsHttpAtom const& aTarget) : mTarget(aTarget) {}
|
|
int operator()(nsHttpAtom const* aVal) const {
|
|
if (mTarget == *aVal) {
|
|
return 0;
|
|
}
|
|
return strcmp(mTarget._val, aVal->_val);
|
|
}
|
|
};
|
|
|
|
size_t unused;
|
|
return BinarySearchIf(blackList, 0, ArrayLength(blackList),
|
|
HttpAtomComparator(aHeader), &unused);
|
|
}
|
|
|
|
class AddHeadersToChannelVisitor final : public nsIHttpHeaderVisitor {
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
|
|
explicit AddHeadersToChannelVisitor(nsIHttpChannel* aChannel)
|
|
: mChannel(aChannel) {}
|
|
|
|
NS_IMETHOD VisitHeader(const nsACString& aHeader,
|
|
const nsACString& aValue) override {
|
|
nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
|
|
if (!IsHeaderBlacklistedForRedirectCopy(atom)) {
|
|
DebugOnly<nsresult> rv =
|
|
mChannel->SetRequestHeader(aHeader, aValue, false);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
private:
|
|
~AddHeadersToChannelVisitor() = default;
|
|
|
|
nsCOMPtr<nsIHttpChannel> mChannel;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(AddHeadersToChannelVisitor, nsIHttpHeaderVisitor)
|
|
|
|
HttpBaseChannel::HttpBaseChannel()
|
|
: mReportCollector(new ConsoleReportCollector()),
|
|
mHttpHandler(gHttpHandler),
|
|
mChannelCreationTime(0),
|
|
mComputedCrossOriginOpenerPolicy(nsILoadInfo::OPENER_POLICY_UNSAFE_NONE),
|
|
mStartPos(UINT64_MAX),
|
|
mTransferSize(0),
|
|
mRequestSize(0),
|
|
mDecodedBodySize(0),
|
|
mEncodedBodySize(0),
|
|
mRequestContextID(0),
|
|
mContentWindowId(0),
|
|
mTopLevelOuterContentWindowId(0),
|
|
mAltDataLength(-1),
|
|
mChannelId(0),
|
|
mReqContentLength(0U),
|
|
mStatus(NS_OK),
|
|
mCanceled(false),
|
|
mFirstPartyClassificationFlags(0),
|
|
mThirdPartyClassificationFlags(0),
|
|
mFlashPluginState(nsIHttpChannel::FlashPluginUnknown),
|
|
mLoadFlags(LOAD_NORMAL),
|
|
mCaps(0),
|
|
mClassOfService(0),
|
|
mUpgradeToSecure(false),
|
|
mApplyConversion(true),
|
|
mHasAppliedConversion(false),
|
|
mIsPending(false),
|
|
mWasOpened(false),
|
|
mRequestObserversCalled(false),
|
|
mResponseHeadersModified(false),
|
|
mAllowSTS(true),
|
|
mThirdPartyFlags(0),
|
|
mUploadStreamHasHeaders(false),
|
|
mInheritApplicationCache(true),
|
|
mChooseApplicationCache(false),
|
|
mLoadedFromApplicationCache(false),
|
|
mChannelIsForDownload(false),
|
|
mTracingEnabled(true),
|
|
mTimingEnabled(false),
|
|
mReportTiming(true),
|
|
mAllowSpdy(true),
|
|
mAllowAltSvc(true),
|
|
mBeConservative(false),
|
|
mIsTRRServiceChannel(false),
|
|
mResolvedByTRR(false),
|
|
mResponseTimeoutEnabled(true),
|
|
mAllRedirectsSameOrigin(true),
|
|
mAllRedirectsPassTimingAllowCheck(true),
|
|
mResponseCouldBeSynthesized(false),
|
|
mBlockAuthPrompt(false),
|
|
mAllowStaleCacheContent(false),
|
|
mPreferCacheLoadOverBypass(false),
|
|
mAddedAsNonTailRequest(false),
|
|
mAsyncOpenWaitingForStreamLength(false),
|
|
mUpgradableToSecure(true),
|
|
mHasNonEmptySandboxingFlag(false),
|
|
mTlsFlags(0),
|
|
mSuspendCount(0),
|
|
mInitialRwin(0),
|
|
mProxyResolveFlags(0),
|
|
mContentDispositionHint(UINT32_MAX),
|
|
mCorsMode(nsIHttpChannelInternal::CORS_MODE_NO_CORS),
|
|
mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW),
|
|
mLastRedirectFlags(0),
|
|
mPriority(PRIORITY_NORMAL),
|
|
mRedirectionLimit(gHttpHandler->RedirectionLimit()),
|
|
mRedirectCount(0),
|
|
mInternalRedirectCount(0),
|
|
mAsyncOpenTimeOverriden(false),
|
|
mForcePending(false),
|
|
mDeliveringAltData(false),
|
|
mCorsIncludeCredentials(false),
|
|
mOnStartRequestCalled(false),
|
|
mOnStopRequestCalled(false),
|
|
mAfterOnStartRequestBegun(false),
|
|
mRequireCORSPreflight(false),
|
|
mAltDataForChild(false),
|
|
mDisableAltDataCache(false),
|
|
mForceMainDocumentChannel(false),
|
|
mPendingInputStreamLengthOperation(false),
|
|
mHasCrossOriginOpenerPolicyMismatch(0) {
|
|
this->mSelfAddr.inet = {};
|
|
this->mPeerAddr.inet = {};
|
|
LOG(("Creating HttpBaseChannel @%p\n", this));
|
|
|
|
// Subfields of unions cannot be targeted in an initializer list.
|
|
#ifdef MOZ_VALGRIND
|
|
// Zero the entire unions so that Valgrind doesn't complain when we send them
|
|
// to another process.
|
|
memset(&mSelfAddr, 0, sizeof(NetAddr));
|
|
memset(&mPeerAddr, 0, sizeof(NetAddr));
|
|
#endif
|
|
mSelfAddr.raw.family = PR_AF_UNSPEC;
|
|
mPeerAddr.raw.family = PR_AF_UNSPEC;
|
|
}
|
|
|
|
HttpBaseChannel::~HttpBaseChannel() {
|
|
LOG(("Destroying HttpBaseChannel @%p\n", this));
|
|
|
|
// Make sure we don't leak
|
|
CleanRedirectCacheChainIfNecessary();
|
|
|
|
ReleaseMainThreadOnlyReferences();
|
|
}
|
|
|
|
namespace { // anon
|
|
|
|
class NonTailRemover : public nsISupports {
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
|
|
explicit NonTailRemover(nsIRequestContext* rc) : mRequestContext(rc) {}
|
|
|
|
private:
|
|
virtual ~NonTailRemover() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
mRequestContext->RemoveNonTailRequest();
|
|
}
|
|
|
|
nsCOMPtr<nsIRequestContext> mRequestContext;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS0(NonTailRemover)
|
|
|
|
} // namespace
|
|
|
|
void HttpBaseChannel::ReleaseMainThreadOnlyReferences() {
|
|
if (NS_IsMainThread()) {
|
|
// Already on main thread, let dtor to
|
|
// take care of releasing references
|
|
RemoveAsNonTailRequest();
|
|
return;
|
|
}
|
|
|
|
nsTArray<nsCOMPtr<nsISupports>> arrayToRelease;
|
|
arrayToRelease.AppendElement(mLoadGroup.forget());
|
|
arrayToRelease.AppendElement(mLoadInfo.forget());
|
|
arrayToRelease.AppendElement(mCallbacks.forget());
|
|
arrayToRelease.AppendElement(mProgressSink.forget());
|
|
arrayToRelease.AppendElement(mApplicationCache.forget());
|
|
arrayToRelease.AppendElement(mPrincipal.forget());
|
|
arrayToRelease.AppendElement(mListener.forget());
|
|
arrayToRelease.AppendElement(mCompressListener.forget());
|
|
|
|
if (mAddedAsNonTailRequest) {
|
|
// RemoveNonTailRequest() on our request context must be called on the main
|
|
// thread
|
|
MOZ_RELEASE_ASSERT(mRequestContext,
|
|
"Someone released rc or set flags w/o having it?");
|
|
|
|
nsCOMPtr<nsISupports> nonTailRemover(new NonTailRemover(mRequestContext));
|
|
arrayToRelease.AppendElement(nonTailRemover.forget());
|
|
}
|
|
|
|
NS_DispatchToMainThread(new ProxyReleaseRunnable(std::move(arrayToRelease)));
|
|
}
|
|
|
|
void HttpBaseChannel::AddClassificationFlags(uint32_t aClassificationFlags,
|
|
bool aIsThirdParty) {
|
|
LOG(
|
|
("HttpBaseChannel::AddClassificationFlags classificationFlags=%d "
|
|
"thirdparty=%d %p",
|
|
aClassificationFlags, static_cast<int>(aIsThirdParty), this));
|
|
|
|
if (aIsThirdParty) {
|
|
mThirdPartyClassificationFlags |= aClassificationFlags;
|
|
} else {
|
|
mFirstPartyClassificationFlags |= aClassificationFlags;
|
|
}
|
|
}
|
|
|
|
void HttpBaseChannel::SetFlashPluginState(
|
|
nsIHttpChannel::FlashPluginState aState) {
|
|
LOG(("HttpBaseChannel::SetFlashPluginState %p", this));
|
|
mFlashPluginState = aState;
|
|
}
|
|
|
|
nsresult HttpBaseChannel::Init(nsIURI* aURI, uint32_t aCaps,
|
|
nsProxyInfo* aProxyInfo,
|
|
uint32_t aProxyResolveFlags, nsIURI* aProxyURI,
|
|
uint64_t aChannelId,
|
|
nsContentPolicyType aContentPolicyType) {
|
|
LOG1(("HttpBaseChannel::Init [this=%p]\n", this));
|
|
|
|
MOZ_ASSERT(aURI, "null uri");
|
|
|
|
mURI = aURI;
|
|
mOriginalURI = aURI;
|
|
mDocumentURI = nullptr;
|
|
mCaps = aCaps;
|
|
mProxyResolveFlags = aProxyResolveFlags;
|
|
mProxyURI = aProxyURI;
|
|
mChannelId = aChannelId;
|
|
|
|
// Construct connection info object
|
|
nsAutoCString host;
|
|
int32_t port = -1;
|
|
bool isHTTPS = mURI->SchemeIs("https");
|
|
|
|
nsresult rv = mURI->GetAsciiHost(host);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Reject the URL if it doesn't specify a host
|
|
if (host.IsEmpty()) return NS_ERROR_MALFORMED_URI;
|
|
|
|
rv = mURI->GetPort(&port);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
LOG1(("host=%s port=%d\n", host.get(), port));
|
|
|
|
rv = mURI->GetAsciiSpec(mSpec);
|
|
if (NS_FAILED(rv)) return rv;
|
|
LOG1(("uri=%s\n", mSpec.get()));
|
|
|
|
// Assert default request method
|
|
MOZ_ASSERT(mRequestHead.EqualsMethod(nsHttpRequestHead::kMethod_Get));
|
|
|
|
// Set request headers
|
|
nsAutoCString hostLine;
|
|
rv = nsHttpHandler::GenerateHostPort(host, port, hostLine);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = mRequestHead.SetHeader(nsHttp::Host, hostLine);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = gHttpHandler->AddStandardRequestHeaders(&mRequestHead, isHTTPS,
|
|
aContentPolicyType);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsAutoCString type;
|
|
if (aProxyInfo && NS_SUCCEEDED(aProxyInfo->GetType(type)) &&
|
|
!type.EqualsLiteral("unknown"))
|
|
mProxyInfo = aProxyInfo;
|
|
|
|
mCurrentThread = GetCurrentEventTarget();
|
|
return rv;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsISupports
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMPL_ADDREF(HttpBaseChannel)
|
|
NS_IMPL_RELEASE(HttpBaseChannel)
|
|
|
|
NS_INTERFACE_MAP_BEGIN(HttpBaseChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIRequest)
|
|
NS_INTERFACE_MAP_ENTRY(nsIChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIIdentChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIEncodedChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
|
|
NS_INTERFACE_MAP_ENTRY(nsIForcePendingChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIFormPOSTActionChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
|
|
NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIPrivateBrowsingChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsITimedChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIConsoleReportCollector)
|
|
NS_INTERFACE_MAP_ENTRY(nsIThrottledInputChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIClassifiedChannel)
|
|
NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpBaseChannel)
|
|
NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIRequest
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetName(nsACString& aName) {
|
|
aName = mSpec;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::IsPending(bool* aIsPending) {
|
|
NS_ENSURE_ARG_POINTER(aIsPending);
|
|
*aIsPending = mIsPending || mForcePending;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetStatus(nsresult* aStatus) {
|
|
NS_ENSURE_ARG_POINTER(aStatus);
|
|
*aStatus = mStatus;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
|
|
NS_ENSURE_ARG_POINTER(aLoadGroup);
|
|
*aLoadGroup = mLoadGroup;
|
|
NS_IF_ADDREF(*aLoadGroup);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
|
|
|
|
if (!CanSetLoadGroup(aLoadGroup)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mLoadGroup = aLoadGroup;
|
|
mProgressSink = nullptr;
|
|
UpdatePrivateBrowsing();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
|
|
NS_ENSURE_ARG_POINTER(aLoadFlags);
|
|
*aLoadFlags = mLoadFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
|
|
mLoadFlags = aLoadFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
|
|
return GetTRRModeImpl(aTRRMode);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
|
|
return SetTRRModeImpl(aTRRMode);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetDocshellUserAgentOverride() {
|
|
RefPtr<dom::BrowsingContext> bc;
|
|
MOZ_ALWAYS_SUCCEEDS(mLoadInfo->GetBrowsingContext(getter_AddRefs(bc)));
|
|
if (!bc) {
|
|
return NS_OK;
|
|
}
|
|
|
|
const nsString& customUserAgent = bc->GetUserAgentOverride();
|
|
if (customUserAgent.IsEmpty() || customUserAgent.IsVoid()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_ConvertUTF16toUTF8 utf8CustomUserAgent(customUserAgent);
|
|
nsresult rv = SetRequestHeader("User-Agent"_ns, utf8CustomUserAgent, false);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIChannel
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetOriginalURI(nsIURI** aOriginalURI) {
|
|
NS_ENSURE_ARG_POINTER(aOriginalURI);
|
|
*aOriginalURI = do_AddRef(mOriginalURI).take();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetOriginalURI(nsIURI* aOriginalURI) {
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
|
|
NS_ENSURE_ARG_POINTER(aOriginalURI);
|
|
mOriginalURI = aOriginalURI;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetURI(nsIURI** aURI) {
|
|
NS_ENSURE_ARG_POINTER(aURI);
|
|
*aURI = do_AddRef(mURI).take();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetOwner(nsISupports** aOwner) {
|
|
NS_ENSURE_ARG_POINTER(aOwner);
|
|
*aOwner = mOwner;
|
|
NS_IF_ADDREF(*aOwner);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetOwner(nsISupports* aOwner) {
|
|
mOwner = aOwner;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
|
|
MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null");
|
|
mLoadInfo = aLoadInfo;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
|
|
NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetIsDocument(bool* aIsDocument) {
|
|
return NS_GetIsDocumentChannel(this, aIsDocument);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
|
|
*aCallbacks = mCallbacks;
|
|
NS_IF_ADDREF(*aCallbacks);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
|
|
|
|
if (!CanSetCallbacks(aCallbacks)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mCallbacks = aCallbacks;
|
|
mProgressSink = nullptr;
|
|
|
|
UpdatePrivateBrowsing();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetContentType(nsACString& aContentType) {
|
|
if (!mResponseHead) {
|
|
aContentType.Truncate();
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
mResponseHead->ContentType(aContentType);
|
|
if (!aContentType.IsEmpty()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
aContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetContentType(const nsACString& aContentType) {
|
|
if (mListener || mWasOpened) {
|
|
if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
nsAutoCString contentTypeBuf, charsetBuf;
|
|
bool hadCharset;
|
|
net_ParseContentType(aContentType, contentTypeBuf, charsetBuf, &hadCharset);
|
|
|
|
mResponseHead->SetContentType(contentTypeBuf);
|
|
|
|
// take care not to stomp on an existing charset
|
|
if (hadCharset) mResponseHead->SetContentCharset(charsetBuf);
|
|
|
|
} else {
|
|
// We are being given a content-type hint.
|
|
bool dummy;
|
|
net_ParseContentType(aContentType, mContentTypeHint, mContentCharsetHint,
|
|
&dummy);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetContentCharset(nsACString& aContentCharset) {
|
|
if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
mResponseHead->ContentCharset(aContentCharset);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetContentCharset(const nsACString& aContentCharset) {
|
|
if (mListener) {
|
|
if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
mResponseHead->SetContentCharset(aContentCharset);
|
|
} else {
|
|
// Charset hint
|
|
mContentCharsetHint = aContentCharset;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetContentDisposition(uint32_t* aContentDisposition) {
|
|
nsresult rv;
|
|
nsCString header;
|
|
|
|
rv = GetContentDispositionHeader(header);
|
|
if (NS_FAILED(rv)) {
|
|
if (mContentDispositionHint == UINT32_MAX) return rv;
|
|
|
|
*aContentDisposition = mContentDispositionHint;
|
|
return NS_OK;
|
|
}
|
|
|
|
*aContentDisposition = NS_GetContentDispositionFromHeader(header, this);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetContentDisposition(uint32_t aContentDisposition) {
|
|
mContentDispositionHint = aContentDisposition;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetContentDispositionFilename(
|
|
nsAString& aContentDispositionFilename) {
|
|
aContentDispositionFilename.Truncate();
|
|
nsresult rv;
|
|
nsCString header;
|
|
|
|
rv = GetContentDispositionHeader(header);
|
|
if (NS_FAILED(rv)) {
|
|
if (!mContentDispositionFilename) return rv;
|
|
|
|
aContentDispositionFilename = *mContentDispositionFilename;
|
|
return NS_OK;
|
|
}
|
|
|
|
return NS_GetFilenameFromDisposition(aContentDispositionFilename, header);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetContentDispositionFilename(
|
|
const nsAString& aContentDispositionFilename) {
|
|
mContentDispositionFilename =
|
|
MakeUnique<nsString>(aContentDispositionFilename);
|
|
|
|
// For safety reasons ensure the filename doesn't contain null characters and
|
|
// replace them with underscores. We may later pass the extension to system
|
|
// MIME APIs that expect null terminated strings.
|
|
mContentDispositionFilename->ReplaceChar(char16_t(0), '_');
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetContentDispositionHeader(
|
|
nsACString& aContentDispositionHeader) {
|
|
if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
nsresult rv = mResponseHead->GetHeader(nsHttp::Content_Disposition,
|
|
aContentDispositionHeader);
|
|
if (NS_FAILED(rv) || aContentDispositionHeader.IsEmpty())
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetContentLength(int64_t* aContentLength) {
|
|
NS_ENSURE_ARG_POINTER(aContentLength);
|
|
|
|
if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
if (mDeliveringAltData) {
|
|
MOZ_ASSERT(!mAvailableCachedAltDataType.IsEmpty());
|
|
*aContentLength = mAltDataLength;
|
|
return NS_OK;
|
|
}
|
|
|
|
*aContentLength = mResponseHead->ContentLength();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetContentLength(int64_t value) {
|
|
MOZ_ASSERT_UNREACHABLE("HttpBaseChannel::SetContentLength");
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::Open(nsIInputStream** aStream) {
|
|
if (!gHttpHandler->Active()) {
|
|
LOG(("HttpBaseChannel::Open after HTTP shutdown..."));
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
nsCOMPtr<nsIStreamListener> listener;
|
|
nsresult rv =
|
|
nsContentSecurityManager::doContentSecurityCheck(this, listener);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_IN_PROGRESS);
|
|
|
|
if (!gHttpHandler->Active()) {
|
|
LOG(("HttpBaseChannel::Open after HTTP shutdown..."));
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
return NS_ImplementChannelOpen(this, aStream);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIUploadChannel
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetUploadStream(nsIInputStream** stream) {
|
|
NS_ENSURE_ARG_POINTER(stream);
|
|
*stream = mUploadStream;
|
|
NS_IF_ADDREF(*stream);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetUploadStream(nsIInputStream* stream,
|
|
const nsACString& contentTypeArg,
|
|
int64_t contentLength) {
|
|
// NOTE: for backwards compatibility and for compatibility with old style
|
|
// plugins, |stream| may include headers, specifically Content-Type and
|
|
// Content-Length headers. in this case, |contentType| and |contentLength|
|
|
// would be unspecified. this is traditionally the case of a POST request,
|
|
// and so we select POST as the request method if contentType and
|
|
// contentLength are unspecified.
|
|
|
|
if (stream) {
|
|
nsAutoCString method;
|
|
bool hasHeaders = false;
|
|
|
|
// This method and ExplicitSetUploadStream mean different things by "empty
|
|
// content type string". This method means "no header", but
|
|
// ExplicitSetUploadStream means "header with empty value". So we have to
|
|
// massage the contentType argument into the form ExplicitSetUploadStream
|
|
// expects.
|
|
nsCOMPtr<nsIMIMEInputStream> mimeStream;
|
|
nsCString contentType(contentTypeArg);
|
|
if (contentType.IsEmpty()) {
|
|
contentType.SetIsVoid(true);
|
|
method = "POST"_ns;
|
|
|
|
// MIME streams are a special case, and include headers which need to be
|
|
// copied to the channel.
|
|
mimeStream = do_QueryInterface(stream);
|
|
if (mimeStream) {
|
|
// Copy non-origin related headers to the channel.
|
|
nsCOMPtr<nsIHttpHeaderVisitor> visitor =
|
|
new AddHeadersToChannelVisitor(this);
|
|
mimeStream->VisitHeaders(visitor);
|
|
|
|
return ExplicitSetUploadStream(stream, contentType, contentLength,
|
|
method, hasHeaders);
|
|
}
|
|
|
|
hasHeaders = true;
|
|
} else {
|
|
method = "PUT"_ns;
|
|
|
|
MOZ_ASSERT(
|
|
NS_FAILED(CallQueryInterface(stream, getter_AddRefs(mimeStream))),
|
|
"nsIMIMEInputStream should not be set with an explicit content type");
|
|
}
|
|
return ExplicitSetUploadStream(stream, contentType, contentLength, method,
|
|
hasHeaders);
|
|
}
|
|
|
|
// if stream is null, ExplicitSetUploadStream returns error.
|
|
// So we need special case for GET method.
|
|
mUploadStreamHasHeaders = false;
|
|
mRequestHead.SetMethod("GET"_ns); // revert to GET request
|
|
mUploadStream = stream;
|
|
return NS_OK;
|
|
}
|
|
|
|
namespace {
|
|
|
|
void CopyComplete(void* aClosure, nsresult aStatus) {
|
|
#ifdef DEBUG
|
|
// Called on the STS thread by NS_AsyncCopy
|
|
nsCOMPtr<nsIEventTarget> sts =
|
|
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
|
|
bool result = false;
|
|
sts->IsOnCurrentThread(&result);
|
|
MOZ_ASSERT(result, "Should only be called on the STS thread.");
|
|
#endif
|
|
|
|
auto channel = static_cast<HttpBaseChannel*>(aClosure);
|
|
channel->OnCopyComplete(aStatus);
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::EnsureUploadStreamIsCloneable(nsIRunnable* aCallback) {
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
|
|
NS_ENSURE_ARG_POINTER(aCallback);
|
|
|
|
// We could in theory allow multiple callers to use this method,
|
|
// but the complexity does not seem worth it yet. Just fail if
|
|
// this is called more than once simultaneously.
|
|
NS_ENSURE_FALSE(mUploadCloneableCallback, NS_ERROR_UNEXPECTED);
|
|
|
|
// We can immediately exec the callback if we don't have an upload stream.
|
|
if (!mUploadStream) {
|
|
aCallback->Run();
|
|
return NS_OK;
|
|
}
|
|
|
|
// Upload nsIInputStream must be cloneable and seekable in order to be
|
|
// processed by devtools network inspector.
|
|
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
|
|
if (seekable && NS_InputStreamIsCloneable(mUploadStream)) {
|
|
aCallback->Run();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIStorageStream> storageStream;
|
|
nsresult rv =
|
|
NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storageStream));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIInputStream> newUploadStream;
|
|
rv = storageStream->NewInputStream(0, getter_AddRefs(newUploadStream));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIOutputStream> sink;
|
|
rv = storageStream->GetOutputStream(0, getter_AddRefs(sink));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIInputStream> source;
|
|
if (NS_InputStreamIsBuffered(mUploadStream)) {
|
|
source = mUploadStream;
|
|
} else {
|
|
rv = NS_NewBufferedInputStream(getter_AddRefs(source),
|
|
mUploadStream.forget(), 4096);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
nsCOMPtr<nsIEventTarget> target =
|
|
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
|
|
|
|
mUploadCloneableCallback = aCallback;
|
|
|
|
rv = NS_AsyncCopy(source, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS,
|
|
4096, // copy segment size
|
|
CopyComplete, this);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
mUploadCloneableCallback = nullptr;
|
|
return rv;
|
|
}
|
|
|
|
// Since we're consuming the old stream, replace it with the new
|
|
// stream immediately.
|
|
mUploadStream = newUploadStream;
|
|
|
|
// Explicity hold the stream alive until copying is complete. This will
|
|
// be released in EnsureUploadStreamIsCloneableComplete().
|
|
AddRef();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void HttpBaseChannel::OnCopyComplete(nsresult aStatus) {
|
|
// Assert in parent process because we don't have to label the runnable
|
|
// in parent process.
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<nsresult>(
|
|
"net::HttpBaseChannel::EnsureUploadStreamIsCloneableComplete", this,
|
|
&HttpBaseChannel::EnsureUploadStreamIsCloneableComplete, aStatus);
|
|
NS_DispatchToMainThread(runnable.forget());
|
|
}
|
|
|
|
void HttpBaseChannel::EnsureUploadStreamIsCloneableComplete(nsresult aStatus) {
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
|
|
MOZ_ASSERT(mUploadCloneableCallback);
|
|
|
|
if (NS_SUCCEEDED(mStatus)) {
|
|
mStatus = aStatus;
|
|
}
|
|
|
|
mUploadCloneableCallback->Run();
|
|
mUploadCloneableCallback = nullptr;
|
|
|
|
// Release the reference we grabbed in EnsureUploadStreamIsCloneable() now
|
|
// that the copying is complete.
|
|
Release();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::CloneUploadStream(int64_t* aContentLength,
|
|
nsIInputStream** aClonedStream) {
|
|
NS_ENSURE_ARG_POINTER(aContentLength);
|
|
NS_ENSURE_ARG_POINTER(aClonedStream);
|
|
*aClonedStream = nullptr;
|
|
|
|
if (!mUploadStream) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIInputStream> clonedStream;
|
|
nsresult rv =
|
|
NS_CloneInputStream(mUploadStream, getter_AddRefs(clonedStream));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
clonedStream.forget(aClonedStream);
|
|
|
|
*aContentLength = mReqContentLength;
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIUploadChannel2
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::ExplicitSetUploadStream(nsIInputStream* aStream,
|
|
const nsACString& aContentType,
|
|
int64_t aContentLength,
|
|
const nsACString& aMethod,
|
|
bool aStreamHasHeaders) {
|
|
// Ensure stream is set and method is valid
|
|
NS_ENSURE_TRUE(aStream, NS_ERROR_FAILURE);
|
|
|
|
{
|
|
DebugOnly<nsCOMPtr<nsIMIMEInputStream>> mimeStream;
|
|
MOZ_ASSERT(
|
|
!aStreamHasHeaders || NS_FAILED(CallQueryInterface(
|
|
aStream, getter_AddRefs(mimeStream.value))),
|
|
"nsIMIMEInputStream should not include headers");
|
|
}
|
|
|
|
nsresult rv = SetRequestMethod(aMethod);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!aStreamHasHeaders && !aContentType.IsVoid()) {
|
|
if (aContentType.IsEmpty()) {
|
|
SetEmptyRequestHeader("Content-Type"_ns);
|
|
} else {
|
|
SetRequestHeader("Content-Type"_ns, aContentType, false);
|
|
}
|
|
}
|
|
|
|
mUploadStreamHasHeaders = aStreamHasHeaders;
|
|
|
|
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aStream);
|
|
if (!seekable) {
|
|
nsCOMPtr<nsIInputStream> stream = aStream;
|
|
seekable = new PartiallySeekableInputStream(stream.forget());
|
|
}
|
|
|
|
mUploadStream = do_QueryInterface(seekable);
|
|
|
|
if (aContentLength >= 0) {
|
|
ExplicitSetUploadStreamLength(aContentLength, aStreamHasHeaders);
|
|
return NS_OK;
|
|
}
|
|
|
|
// Sync access to the stream length.
|
|
int64_t length;
|
|
if (InputStreamLengthHelper::GetSyncLength(aStream, &length)) {
|
|
ExplicitSetUploadStreamLength(length >= 0 ? length : 0, aStreamHasHeaders);
|
|
return NS_OK;
|
|
}
|
|
|
|
// Let's resolve the size of the stream.
|
|
RefPtr<HttpBaseChannel> self = this;
|
|
InputStreamLengthHelper::GetAsyncLength(
|
|
aStream, [self, aStreamHasHeaders](int64_t aLength) {
|
|
self->mPendingInputStreamLengthOperation = false;
|
|
self->ExplicitSetUploadStreamLength(aLength >= 0 ? aLength : 0,
|
|
aStreamHasHeaders);
|
|
self->MaybeResumeAsyncOpen();
|
|
});
|
|
mPendingInputStreamLengthOperation = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
void HttpBaseChannel::ExplicitSetUploadStreamLength(uint64_t aContentLength,
|
|
bool aStreamHasHeaders) {
|
|
// We already have the content length. We don't need to determinate it.
|
|
mReqContentLength = aContentLength;
|
|
|
|
if (aStreamHasHeaders) {
|
|
return;
|
|
}
|
|
|
|
nsAutoCString header;
|
|
header.AssignLiteral("Content-Length");
|
|
|
|
// Maybe the content-length header has been already set.
|
|
nsAutoCString value;
|
|
nsresult rv = GetRequestHeader(header, value);
|
|
if (NS_SUCCEEDED(rv) && !value.IsEmpty()) {
|
|
return;
|
|
}
|
|
|
|
// SetRequestHeader propagates headers to chrome if HttpChannelChild
|
|
MOZ_ASSERT(!mWasOpened);
|
|
nsAutoCString contentLengthStr;
|
|
contentLengthStr.AppendInt(aContentLength);
|
|
SetRequestHeader(header, contentLengthStr, false);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetUploadStreamHasHeaders(bool* hasHeaders) {
|
|
NS_ENSURE_ARG(hasHeaders);
|
|
|
|
*hasHeaders = mUploadStreamHasHeaders;
|
|
return NS_OK;
|
|
}
|
|
|
|
bool HttpBaseChannel::MaybeWaitForUploadStreamLength(
|
|
nsIStreamListener* aListener, nsISupports* aContext) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(!mAsyncOpenWaitingForStreamLength, "AsyncOpen() called twice?");
|
|
|
|
if (!mPendingInputStreamLengthOperation) {
|
|
return false;
|
|
}
|
|
|
|
mListener = aListener;
|
|
mAsyncOpenWaitingForStreamLength = true;
|
|
return true;
|
|
}
|
|
|
|
void HttpBaseChannel::MaybeResumeAsyncOpen() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(!mPendingInputStreamLengthOperation);
|
|
|
|
if (!mAsyncOpenWaitingForStreamLength) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIStreamListener> listener;
|
|
listener.swap(mListener);
|
|
|
|
mAsyncOpenWaitingForStreamLength = false;
|
|
|
|
nsresult rv = AsyncOpen(listener);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
DoAsyncAbort(rv);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIEncodedChannel
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetApplyConversion(bool* value) {
|
|
*value = mApplyConversion;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetApplyConversion(bool value) {
|
|
LOG(("HttpBaseChannel::SetApplyConversion [this=%p value=%d]\n", this,
|
|
value));
|
|
mApplyConversion = value;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult HttpBaseChannel::DoApplyContentConversions(
|
|
nsIStreamListener* aNextListener, nsIStreamListener** aNewNextListener) {
|
|
return DoApplyContentConversions(aNextListener, aNewNextListener, nullptr);
|
|
}
|
|
|
|
// create a listener chain that looks like this
|
|
// http-channel -> decompressor (n times) -> InterceptFailedOnSTop ->
|
|
// channel-creator-listener
|
|
//
|
|
// we need to do this because not every decompressor has fully streamed output
|
|
// so may need a call to OnStopRequest to identify its completion state.. and if
|
|
// it creates an error there the channel status code needs to be updated before
|
|
// calling the terminal listener. Having the decompress do it via cancel() means
|
|
// channels cannot effectively be used in two contexts (specifically this one
|
|
// and a peek context for sniffing)
|
|
//
|
|
class InterceptFailedOnStop : public nsIStreamListener {
|
|
virtual ~InterceptFailedOnStop() = default;
|
|
nsCOMPtr<nsIStreamListener> mNext;
|
|
HttpBaseChannel* mChannel;
|
|
|
|
public:
|
|
InterceptFailedOnStop(nsIStreamListener* arg, HttpBaseChannel* chan)
|
|
: mNext(arg), mChannel(chan) {}
|
|
NS_DECL_THREADSAFE_ISUPPORTS
|
|
|
|
NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override {
|
|
return mNext->OnStartRequest(aRequest);
|
|
}
|
|
|
|
NS_IMETHOD OnStopRequest(nsIRequest* aRequest,
|
|
nsresult aStatusCode) override {
|
|
if (NS_FAILED(aStatusCode) && NS_SUCCEEDED(mChannel->mStatus)) {
|
|
LOG(("HttpBaseChannel::InterceptFailedOnStop %p seting status %" PRIx32,
|
|
mChannel, static_cast<uint32_t>(aStatusCode)));
|
|
mChannel->mStatus = aStatusCode;
|
|
}
|
|
return mNext->OnStopRequest(aRequest, aStatusCode);
|
|
}
|
|
|
|
NS_IMETHOD OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream,
|
|
uint64_t aOffset, uint32_t aCount) override {
|
|
return mNext->OnDataAvailable(aRequest, aInputStream, aOffset, aCount);
|
|
}
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(InterceptFailedOnStop, nsIStreamListener, nsIRequestObserver)
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener,
|
|
nsIStreamListener** aNewNextListener,
|
|
nsISupports* aCtxt) {
|
|
*aNewNextListener = nullptr;
|
|
if (!mResponseHead || !aNextListener) {
|
|
return NS_OK;
|
|
}
|
|
|
|
LOG(("HttpBaseChannel::DoApplyContentConversions [this=%p]\n", this));
|
|
|
|
if (!mApplyConversion) {
|
|
LOG(("not applying conversion per mApplyConversion\n"));
|
|
return NS_OK;
|
|
}
|
|
|
|
if (mDeliveringAltData) {
|
|
MOZ_ASSERT(!mAvailableCachedAltDataType.IsEmpty());
|
|
LOG(("not applying conversion because delivering alt-data\n"));
|
|
return NS_OK;
|
|
}
|
|
|
|
nsAutoCString contentEncoding;
|
|
nsresult rv =
|
|
mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
|
|
if (NS_FAILED(rv) || contentEncoding.IsEmpty()) return NS_OK;
|
|
|
|
nsCOMPtr<nsIStreamListener> nextListener =
|
|
new InterceptFailedOnStop(aNextListener, this);
|
|
|
|
// The encodings are listed in the order they were applied
|
|
// (see rfc 2616 section 14.11), so they need to removed in reverse
|
|
// order. This is accomplished because the converter chain ends up
|
|
// being a stack with the last converter created being the first one
|
|
// to accept the raw network data.
|
|
|
|
char* cePtr = contentEncoding.BeginWriting();
|
|
uint32_t count = 0;
|
|
while (char* val = nsCRT::strtok(cePtr, HTTP_LWS ",", &cePtr)) {
|
|
if (++count > 16) {
|
|
// That's ridiculous. We only understand 2 different ones :)
|
|
// but for compatibility with old code, we will just carry on without
|
|
// removing the encodings
|
|
LOG(("Too many Content-Encodings. Ignoring remainder.\n"));
|
|
break;
|
|
}
|
|
|
|
if (gHttpHandler->IsAcceptableEncoding(val, mURI->SchemeIs("https"))) {
|
|
nsCOMPtr<nsIStreamConverterService> serv;
|
|
rv = gHttpHandler->GetStreamConverterService(getter_AddRefs(serv));
|
|
|
|
// we won't fail to load the page just because we couldn't load the
|
|
// stream converter service.. carry on..
|
|
if (NS_FAILED(rv)) {
|
|
if (val) LOG(("Unknown content encoding '%s', ignoring\n", val));
|
|
continue;
|
|
}
|
|
|
|
nsCOMPtr<nsIStreamListener> converter;
|
|
nsAutoCString from(val);
|
|
ToLowerCase(from);
|
|
rv = serv->AsyncConvertData(from.get(), "uncompressed", nextListener,
|
|
aCtxt, getter_AddRefs(converter));
|
|
if (NS_FAILED(rv)) {
|
|
LOG(("Unexpected failure of AsyncConvertData %s\n", val));
|
|
return rv;
|
|
}
|
|
|
|
LOG(("converter removed '%s' content-encoding\n", val));
|
|
if (Telemetry::CanRecordPrereleaseData()) {
|
|
int mode = 0;
|
|
if (from.EqualsLiteral("gzip") || from.EqualsLiteral("x-gzip")) {
|
|
mode = 1;
|
|
} else if (from.EqualsLiteral("deflate") ||
|
|
from.EqualsLiteral("x-deflate")) {
|
|
mode = 2;
|
|
} else if (from.EqualsLiteral("br")) {
|
|
mode = 3;
|
|
}
|
|
Telemetry::Accumulate(Telemetry::HTTP_CONTENT_ENCODING, mode);
|
|
}
|
|
nextListener = converter;
|
|
} else {
|
|
if (val) LOG(("Unknown content encoding '%s', ignoring\n", val));
|
|
}
|
|
}
|
|
*aNewNextListener = nextListener;
|
|
NS_IF_ADDREF(*aNewNextListener);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetContentEncodings(nsIUTF8StringEnumerator** aEncodings) {
|
|
if (!mResponseHead) {
|
|
*aEncodings = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsAutoCString encoding;
|
|
Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, encoding);
|
|
if (encoding.IsEmpty()) {
|
|
*aEncodings = nullptr;
|
|
return NS_OK;
|
|
}
|
|
RefPtr<nsContentEncodings> enumerator =
|
|
new nsContentEncodings(this, encoding.get());
|
|
enumerator.forget(aEncodings);
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsContentEncodings <public>
|
|
//-----------------------------------------------------------------------------
|
|
|
|
HttpBaseChannel::nsContentEncodings::nsContentEncodings(
|
|
nsIHttpChannel* aChannel, const char* aEncodingHeader)
|
|
: mEncodingHeader(aEncodingHeader), mChannel(aChannel), mReady(false) {
|
|
mCurEnd = aEncodingHeader + strlen(aEncodingHeader);
|
|
mCurStart = mCurEnd;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsContentEncodings::nsISimpleEnumerator
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::nsContentEncodings::HasMore(bool* aMoreEncodings) {
|
|
if (mReady) {
|
|
*aMoreEncodings = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult rv = PrepareForNext();
|
|
*aMoreEncodings = NS_SUCCEEDED(rv);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::nsContentEncodings::GetNext(nsACString& aNextEncoding) {
|
|
aNextEncoding.Truncate();
|
|
if (!mReady) {
|
|
nsresult rv = PrepareForNext();
|
|
if (NS_FAILED(rv)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
const nsACString& encoding = Substring(mCurStart, mCurEnd);
|
|
|
|
nsACString::const_iterator start, end;
|
|
encoding.BeginReading(start);
|
|
encoding.EndReading(end);
|
|
|
|
bool haveType = false;
|
|
if (CaseInsensitiveFindInReadable("gzip"_ns, start, end)) {
|
|
aNextEncoding.AssignLiteral(APPLICATION_GZIP);
|
|
haveType = true;
|
|
}
|
|
|
|
if (!haveType) {
|
|
encoding.BeginReading(start);
|
|
if (CaseInsensitiveFindInReadable("compress"_ns, start, end)) {
|
|
aNextEncoding.AssignLiteral(APPLICATION_COMPRESS);
|
|
haveType = true;
|
|
}
|
|
}
|
|
|
|
if (!haveType) {
|
|
encoding.BeginReading(start);
|
|
if (CaseInsensitiveFindInReadable("deflate"_ns, start, end)) {
|
|
aNextEncoding.AssignLiteral(APPLICATION_ZIP);
|
|
haveType = true;
|
|
}
|
|
}
|
|
|
|
if (!haveType) {
|
|
encoding.BeginReading(start);
|
|
if (CaseInsensitiveFindInReadable("br"_ns, start, end)) {
|
|
aNextEncoding.AssignLiteral(APPLICATION_BROTLI);
|
|
haveType = true;
|
|
}
|
|
}
|
|
|
|
// Prepare to fetch the next encoding
|
|
mCurEnd = mCurStart;
|
|
mReady = false;
|
|
|
|
if (haveType) return NS_OK;
|
|
|
|
NS_WARNING("Unknown encoding type");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsContentEncodings::nsISupports
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMPL_ISUPPORTS(HttpBaseChannel::nsContentEncodings, nsIUTF8StringEnumerator,
|
|
nsIStringEnumerator)
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsContentEncodings <private>
|
|
//-----------------------------------------------------------------------------
|
|
|
|
nsresult HttpBaseChannel::nsContentEncodings::PrepareForNext(void) {
|
|
MOZ_ASSERT(mCurStart == mCurEnd, "Indeterminate state");
|
|
|
|
// At this point both mCurStart and mCurEnd point to somewhere
|
|
// past the end of the next thing we want to return
|
|
|
|
while (mCurEnd != mEncodingHeader) {
|
|
--mCurEnd;
|
|
if (*mCurEnd != ',' && !nsCRT::IsAsciiSpace(*mCurEnd)) break;
|
|
}
|
|
if (mCurEnd == mEncodingHeader)
|
|
return NS_ERROR_NOT_AVAILABLE; // no more encodings
|
|
++mCurEnd;
|
|
|
|
// At this point mCurEnd points to the first char _after_ the
|
|
// header we want. Furthermore, mCurEnd - 1 != mEncodingHeader
|
|
|
|
mCurStart = mCurEnd - 1;
|
|
while (mCurStart != mEncodingHeader && *mCurStart != ',' &&
|
|
!nsCRT::IsAsciiSpace(*mCurStart))
|
|
--mCurStart;
|
|
if (*mCurStart == ',' || nsCRT::IsAsciiSpace(*mCurStart))
|
|
++mCurStart; // we stopped because of a weird char, so move up one
|
|
|
|
// At this point mCurStart and mCurEnd bracket the encoding string
|
|
// we want. Check that it's not "identity"
|
|
if (Substring(mCurStart, mCurEnd)
|
|
.Equals("identity", nsCaseInsensitiveCStringComparator)) {
|
|
mCurEnd = mCurStart;
|
|
return PrepareForNext();
|
|
}
|
|
|
|
mReady = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIHttpChannel
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetChannelId(uint64_t* aChannelId) {
|
|
NS_ENSURE_ARG_POINTER(aChannelId);
|
|
*aChannelId = mChannelId;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetChannelId(uint64_t aChannelId) {
|
|
mChannelId = aChannelId;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP HttpBaseChannel::GetTopLevelContentWindowId(uint64_t* aWindowId) {
|
|
if (!mContentWindowId) {
|
|
nsCOMPtr<nsILoadContext> loadContext;
|
|
GetCallback(loadContext);
|
|
if (loadContext) {
|
|
nsCOMPtr<mozIDOMWindowProxy> topWindow;
|
|
loadContext->GetTopWindow(getter_AddRefs(topWindow));
|
|
nsCOMPtr<nsIDOMWindowUtils> windowUtils;
|
|
if (topWindow) {
|
|
windowUtils = nsGlobalWindowOuter::Cast(topWindow)->WindowUtils();
|
|
}
|
|
if (windowUtils) {
|
|
windowUtils->GetCurrentInnerWindowID(&mContentWindowId);
|
|
}
|
|
}
|
|
}
|
|
*aWindowId = mContentWindowId;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP HttpBaseChannel::SetTopLevelOuterContentWindowId(
|
|
uint64_t aWindowId) {
|
|
mTopLevelOuterContentWindowId = aWindowId;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP HttpBaseChannel::GetTopLevelOuterContentWindowId(
|
|
uint64_t* aWindowId) {
|
|
EnsureTopLevelOuterContentWindowId();
|
|
*aWindowId = mTopLevelOuterContentWindowId;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP HttpBaseChannel::SetTopLevelContentWindowId(uint64_t aWindowId) {
|
|
mContentWindowId = aWindowId;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::IsThirdPartyTrackingResource(bool* aIsTrackingResource) {
|
|
MOZ_ASSERT(
|
|
!(mFirstPartyClassificationFlags && mThirdPartyClassificationFlags));
|
|
*aIsTrackingResource = UrlClassifierCommon::IsTrackingClassificationFlag(
|
|
mThirdPartyClassificationFlags);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::IsThirdPartySocialTrackingResource(
|
|
bool* aIsThirdPartySocialTrackingResource) {
|
|
MOZ_ASSERT(!mFirstPartyClassificationFlags ||
|
|
!mThirdPartyClassificationFlags);
|
|
*aIsThirdPartySocialTrackingResource =
|
|
UrlClassifierCommon::IsSocialTrackingClassificationFlag(
|
|
mThirdPartyClassificationFlags);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetClassificationFlags(uint32_t* aFlags) {
|
|
if (mThirdPartyClassificationFlags) {
|
|
*aFlags = mThirdPartyClassificationFlags;
|
|
} else {
|
|
*aFlags = mFirstPartyClassificationFlags;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetFirstPartyClassificationFlags(uint32_t* aFlags) {
|
|
*aFlags = mFirstPartyClassificationFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetThirdPartyClassificationFlags(uint32_t* aFlags) {
|
|
*aFlags = mThirdPartyClassificationFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetFlashPluginState(nsIHttpChannel::FlashPluginState* aState) {
|
|
uint32_t flashPluginState = mFlashPluginState;
|
|
*aState = (nsIHttpChannel::FlashPluginState)flashPluginState;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetTransferSize(uint64_t* aTransferSize) {
|
|
*aTransferSize = mTransferSize;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRequestSize(uint64_t* aRequestSize) {
|
|
*aRequestSize = mRequestSize;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetDecodedBodySize(uint64_t* aDecodedBodySize) {
|
|
*aDecodedBodySize = mDecodedBodySize;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetEncodedBodySize(uint64_t* aEncodedBodySize) {
|
|
*aEncodedBodySize = mEncodedBodySize;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRequestMethod(nsACString& aMethod) {
|
|
mRequestHead.Method(aMethod);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetRequestMethod(const nsACString& aMethod) {
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
|
|
const nsCString& flatMethod = PromiseFlatCString(aMethod);
|
|
|
|
// Method names are restricted to valid HTTP tokens.
|
|
if (!nsHttp::IsValidToken(flatMethod)) return NS_ERROR_INVALID_ARG;
|
|
|
|
mRequestHead.SetMethod(flatMethod);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
|
|
NS_ENSURE_ARG_POINTER(aReferrerInfo);
|
|
*aReferrerInfo = do_AddRef(mReferrerInfo).take();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult HttpBaseChannel::SetReferrerInfoInternal(
|
|
nsIReferrerInfo* aReferrerInfo, bool aClone, bool aCompute,
|
|
bool aRespectBeforeConnect) {
|
|
LOG(
|
|
("HttpBaseChannel::SetReferrerInfoInternal [this=%p aClone(%d) "
|
|
"aCompute(%d)]\n",
|
|
this, aClone, aCompute));
|
|
if (aRespectBeforeConnect) {
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
}
|
|
|
|
mReferrerInfo = aReferrerInfo;
|
|
|
|
// clear existing referrer, if any
|
|
nsresult rv = ClearReferrerHeader();
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
if (!mReferrerInfo) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (aClone) {
|
|
mReferrerInfo = static_cast<dom::ReferrerInfo*>(aReferrerInfo)->Clone();
|
|
}
|
|
|
|
dom::ReferrerInfo* referrerInfo =
|
|
static_cast<dom::ReferrerInfo*>(mReferrerInfo.get());
|
|
|
|
// Don't set referrerInfo if it has not been initialized.
|
|
if (!referrerInfo->IsInitialized()) {
|
|
mReferrerInfo = nullptr;
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
}
|
|
|
|
if (aCompute) {
|
|
rv = referrerInfo->ComputeReferrer(this);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> computedReferrer = mReferrerInfo->GetComputedReferrer();
|
|
if (!computedReferrer) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsAutoCString spec;
|
|
rv = computedReferrer->GetSpec(spec);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
return SetReferrerHeader(spec, aRespectBeforeConnect);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
|
|
return SetReferrerInfoInternal(aReferrerInfo, true, true, true);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetReferrerInfoWithoutClone(nsIReferrerInfo* aReferrerInfo) {
|
|
return SetReferrerInfoInternal(aReferrerInfo, false, true, true);
|
|
}
|
|
|
|
// Return the channel's proxy URI, or if it doesn't exist, the
|
|
// channel's main URI.
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetProxyURI(nsIURI** aOut) {
|
|
NS_ENSURE_ARG_POINTER(aOut);
|
|
nsCOMPtr<nsIURI> result(mProxyURI);
|
|
result.forget(aOut);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRequestHeader(const nsACString& aHeader,
|
|
nsACString& aValue) {
|
|
aValue.Truncate();
|
|
|
|
// XXX might be better to search the header list directly instead of
|
|
// hitting the http atom hash table.
|
|
nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
|
|
if (!atom) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
return mRequestHead.GetHeader(atom, aValue);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetRequestHeader(const nsACString& aHeader,
|
|
const nsACString& aValue, bool aMerge) {
|
|
const nsCString& flatHeader = PromiseFlatCString(aHeader);
|
|
const nsCString& flatValue = PromiseFlatCString(aValue);
|
|
|
|
LOG(
|
|
("HttpBaseChannel::SetRequestHeader [this=%p header=\"%s\" value=\"%s\" "
|
|
"merge=%u]\n",
|
|
this, flatHeader.get(), flatValue.get(), aMerge));
|
|
|
|
// Verify header names are valid HTTP tokens and header values are reasonably
|
|
// close to whats allowed in RFC 2616.
|
|
if (!nsHttp::IsValidToken(flatHeader) ||
|
|
!nsHttp::IsReasonableHeaderValue(flatValue)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
return mRequestHead.SetHeader(aHeader, flatValue, aMerge);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetEmptyRequestHeader(const nsACString& aHeader) {
|
|
const nsCString& flatHeader = PromiseFlatCString(aHeader);
|
|
|
|
LOG(("HttpBaseChannel::SetEmptyRequestHeader [this=%p header=\"%s\"]\n", this,
|
|
flatHeader.get()));
|
|
|
|
// Verify header names are valid HTTP tokens and header values are reasonably
|
|
// close to whats allowed in RFC 2616.
|
|
if (!nsHttp::IsValidToken(flatHeader)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
return mRequestHead.SetEmptyHeader(aHeader);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::VisitRequestHeaders(nsIHttpHeaderVisitor* visitor) {
|
|
return mRequestHead.VisitHeaders(visitor);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::VisitNonDefaultRequestHeaders(nsIHttpHeaderVisitor* visitor) {
|
|
return mRequestHead.VisitHeaders(visitor,
|
|
nsHttpHeaderArray::eFilterSkipDefault);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetResponseHeader(const nsACString& header,
|
|
nsACString& value) {
|
|
value.Truncate();
|
|
|
|
if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
nsHttpAtom atom = nsHttp::ResolveAtom(header);
|
|
if (!atom) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
return mResponseHead->GetHeader(atom, value);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetResponseHeader(const nsACString& header,
|
|
const nsACString& value, bool merge) {
|
|
LOG(
|
|
("HttpBaseChannel::SetResponseHeader [this=%p header=\"%s\" value=\"%s\" "
|
|
"merge=%u]\n",
|
|
this, PromiseFlatCString(header).get(), PromiseFlatCString(value).get(),
|
|
merge));
|
|
|
|
if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
nsHttpAtom atom = nsHttp::ResolveAtom(header);
|
|
if (!atom) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
// these response headers must not be changed
|
|
if (atom == nsHttp::Content_Type || atom == nsHttp::Content_Length ||
|
|
atom == nsHttp::Content_Encoding || atom == nsHttp::Trailer ||
|
|
atom == nsHttp::Transfer_Encoding)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
mResponseHeadersModified = true;
|
|
|
|
return mResponseHead->SetHeader(header, value, merge);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::VisitResponseHeaders(nsIHttpHeaderVisitor* visitor) {
|
|
if (!mResponseHead) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
return mResponseHead->VisitHeaders(visitor,
|
|
nsHttpHeaderArray::eFilterResponse);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetOriginalResponseHeader(const nsACString& aHeader,
|
|
nsIHttpHeaderVisitor* aVisitor) {
|
|
if (!mResponseHead) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
|
|
if (!atom) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
return mResponseHead->GetOriginalHeader(atom, aVisitor);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::VisitOriginalResponseHeaders(nsIHttpHeaderVisitor* aVisitor) {
|
|
if (!mResponseHead) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
return mResponseHead->VisitHeaders(
|
|
aVisitor, nsHttpHeaderArray::eFilterResponseOriginal);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetAllowPipelining(bool* value) {
|
|
NS_ENSURE_ARG_POINTER(value);
|
|
*value = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetAllowPipelining(bool value) {
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
// nop
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetAllowSTS(bool* value) {
|
|
NS_ENSURE_ARG_POINTER(value);
|
|
*value = mAllowSTS;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetAllowSTS(bool value) {
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
|
|
mAllowSTS = value;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRedirectionLimit(uint32_t* value) {
|
|
NS_ENSURE_ARG_POINTER(value);
|
|
*value = mRedirectionLimit;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetRedirectionLimit(uint32_t value) {
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
|
|
mRedirectionLimit = std::min<uint32_t>(value, 0xff);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult HttpBaseChannel::OverrideSecurityInfo(nsISupports* aSecurityInfo) {
|
|
MOZ_ASSERT(!mSecurityInfo,
|
|
"This can only be called when we don't have a security info "
|
|
"object already");
|
|
MOZ_RELEASE_ASSERT(
|
|
aSecurityInfo,
|
|
"This can only be called with a valid security info object");
|
|
MOZ_ASSERT(!BypassServiceWorker(),
|
|
"This can only be called on channels that are not bypassing "
|
|
"interception");
|
|
MOZ_ASSERT(mResponseCouldBeSynthesized,
|
|
"This can only be called on channels that can be intercepted");
|
|
if (mSecurityInfo) {
|
|
LOG(
|
|
("HttpBaseChannel::OverrideSecurityInfo mSecurityInfo is null! "
|
|
"[this=%p]\n",
|
|
this));
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
if (!mResponseCouldBeSynthesized) {
|
|
LOG(
|
|
("HttpBaseChannel::OverrideSecurityInfo channel cannot be intercepted! "
|
|
"[this=%p]\n",
|
|
this));
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
mSecurityInfo = aSecurityInfo;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::IsNoStoreResponse(bool* value) {
|
|
if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
|
|
*value = mResponseHead->NoStore();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::IsNoCacheResponse(bool* value) {
|
|
if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
|
|
*value = mResponseHead->NoCache();
|
|
if (!*value) *value = mResponseHead->ExpiresInPast();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::IsPrivateResponse(bool* value) {
|
|
if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
|
|
*value = mResponseHead->Private();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetResponseStatus(uint32_t* aValue) {
|
|
if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
|
|
*aValue = mResponseHead->Status();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetResponseStatusText(nsACString& aValue) {
|
|
if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
|
|
mResponseHead->StatusText(aValue);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRequestSucceeded(bool* aValue) {
|
|
if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
|
|
uint32_t status = mResponseHead->Status();
|
|
*aValue = (status / 100 == 2);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::RedirectTo(nsIURI* targetURI) {
|
|
NS_ENSURE_ARG(targetURI);
|
|
|
|
nsAutoCString spec;
|
|
targetURI->GetAsciiSpec(spec);
|
|
LOG(("HttpBaseChannel::RedirectTo [this=%p, uri=%s]", this, spec.get()));
|
|
LogCallingScriptLocation(this);
|
|
|
|
// We cannot redirect after OnStartRequest of the listener
|
|
// has been called, since to redirect we have to switch channels
|
|
// and the dance with OnStartRequest et al has to start over.
|
|
// This would break the nsIStreamListener contract.
|
|
NS_ENSURE_FALSE(mOnStartRequestCalled, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
mAPIRedirectToURI = targetURI;
|
|
// Only Web Extensions are allowed to redirect a channel to a data:
|
|
// URI. To avoid any bypasses after the channel was flagged by
|
|
// the WebRequst API, we are dropping the flag here.
|
|
mLoadInfo->SetAllowInsecureRedirectToDataURI(false);
|
|
|
|
// We may want to rewrite origin allowance, hence we need an
|
|
// artificial response head.
|
|
if (!mResponseHead) {
|
|
mResponseHead.reset(new nsHttpResponseHead());
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::UpgradeToSecure() {
|
|
// Upgrades are handled internally between http-on-modify-request and
|
|
// http-on-before-connect, which means upgrades are only possible during
|
|
// on-modify, or WebRequest.onBeforeRequest in Web Extensions. Once we are
|
|
// past the code path where upgrades are handled, attempting an upgrade
|
|
// will throw an error.
|
|
NS_ENSURE_TRUE(mUpgradableToSecure, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
mUpgradeToSecure = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRequestContextID(uint64_t* aRCID) {
|
|
NS_ENSURE_ARG_POINTER(aRCID);
|
|
*aRCID = mRequestContextID;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetRequestContextID(uint64_t aRCID) {
|
|
mRequestContextID = aRCID;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetIsMainDocumentChannel(bool* aValue) {
|
|
NS_ENSURE_ARG_POINTER(aValue);
|
|
*aValue = IsNavigation();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetIsMainDocumentChannel(bool aValue) {
|
|
mForceMainDocumentChannel = aValue;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetProtocolVersion(nsACString& aProtocolVersion) {
|
|
nsresult rv;
|
|
nsCOMPtr<nsITransportSecurityInfo> info =
|
|
do_QueryInterface(mSecurityInfo, &rv);
|
|
nsAutoCString protocol;
|
|
if (NS_SUCCEEDED(rv) && info &&
|
|
NS_SUCCEEDED(info->GetNegotiatedNPN(protocol)) && !protocol.IsEmpty()) {
|
|
// The negotiated protocol was not empty so we can use it.
|
|
aProtocolVersion = protocol;
|
|
return NS_OK;
|
|
}
|
|
|
|
if (mResponseHead) {
|
|
HttpVersion version = mResponseHead->Version();
|
|
aProtocolVersion.Assign(nsHttp::GetProtocolVersion(version));
|
|
return NS_OK;
|
|
}
|
|
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIHttpChannelInternal
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetTopWindowURIIfUnknown(nsIURI* aTopWindowURI) {
|
|
if (!aTopWindowURI) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
if (mTopWindowURI) {
|
|
LOG(
|
|
("HttpChannelBase::SetTopWindowURIIfUnknown [this=%p] "
|
|
"mTopWindowURI is already set.\n",
|
|
this));
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> topWindowURI;
|
|
Unused << GetTopWindowURI(getter_AddRefs(topWindowURI));
|
|
|
|
// Don't modify |mTopWindowURI| if we can get one from GetTopWindowURI().
|
|
if (topWindowURI) {
|
|
LOG(
|
|
("HttpChannelBase::SetTopWindowURIIfUnknown [this=%p] "
|
|
"Return an error since we got a top window uri.\n",
|
|
this));
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mTopWindowURI = aTopWindowURI;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetTopWindowURI(nsIURI** aTopWindowURI) {
|
|
nsCOMPtr<nsIURI> uriBeingLoaded =
|
|
AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(this);
|
|
return GetTopWindowURI(uriBeingLoaded, aTopWindowURI);
|
|
}
|
|
|
|
nsresult HttpBaseChannel::GetTopWindowURI(nsIURI* aURIBeingLoaded,
|
|
nsIURI** aTopWindowURI) {
|
|
nsresult rv = NS_OK;
|
|
nsCOMPtr<mozIThirdPartyUtil> util;
|
|
// Only compute the top window URI once. In e10s, this must be computed in the
|
|
// child. The parent gets the top window URI through HttpChannelOpenArgs.
|
|
if (!mTopWindowURI) {
|
|
util = services::GetThirdPartyUtil();
|
|
if (!util) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
nsCOMPtr<mozIDOMWindowProxy> win;
|
|
rv = util->GetTopWindowForChannel(this, aURIBeingLoaded,
|
|
getter_AddRefs(win));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = util->GetURIFromWindow(win, getter_AddRefs(mTopWindowURI));
|
|
#if DEBUG
|
|
if (mTopWindowURI) {
|
|
nsCString spec;
|
|
if (NS_SUCCEEDED(mTopWindowURI->GetSpec(spec))) {
|
|
LOG(("HttpChannelBase::Setting topwindow URI spec %s [this=%p]\n",
|
|
spec.get(), this));
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
NS_IF_ADDREF(*aTopWindowURI = mTopWindowURI);
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetDocumentURI(nsIURI** aDocumentURI) {
|
|
NS_ENSURE_ARG_POINTER(aDocumentURI);
|
|
*aDocumentURI = mDocumentURI;
|
|
NS_IF_ADDREF(*aDocumentURI);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetDocumentURI(nsIURI* aDocumentURI) {
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
|
|
mDocumentURI = aDocumentURI;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRequestVersion(uint32_t* major, uint32_t* minor) {
|
|
HttpVersion version = mRequestHead.Version();
|
|
|
|
if (major) {
|
|
*major = static_cast<uint32_t>(version) / 10;
|
|
}
|
|
if (minor) {
|
|
*minor = static_cast<uint32_t>(version) % 10;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetResponseVersion(uint32_t* major, uint32_t* minor) {
|
|
if (!mResponseHead) {
|
|
*major = *minor = 0; // we should at least be kind about it
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
HttpVersion version = mResponseHead->Version();
|
|
|
|
if (major) {
|
|
*major = static_cast<uint32_t>(version) / 10;
|
|
}
|
|
if (minor) {
|
|
*minor = static_cast<uint32_t>(version) % 10;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void HttpBaseChannel::NotifySetCookie(const nsACString& aCookie) {
|
|
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
|
if (obs) {
|
|
obs->NotifyObservers(static_cast<nsIChannel*>(this),
|
|
"http-on-response-set-cookie",
|
|
NS_ConvertASCIItoUTF16(aCookie).get());
|
|
}
|
|
}
|
|
|
|
bool HttpBaseChannel::IsBrowsingContextDiscarded() const {
|
|
if (mLoadGroup && mLoadGroup->GetIsBrowsingContextDiscarded()) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// https://mikewest.github.io/corpp/#process-navigation-response
|
|
nsresult HttpBaseChannel::ProcessCrossOriginEmbedderPolicyHeader() {
|
|
nsresult rv;
|
|
if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Only consider Cross-Origin-Embedder-Policy for document loads.
|
|
if (mLoadInfo->GetExternalContentPolicyType() !=
|
|
nsIContentPolicy::TYPE_DOCUMENT &&
|
|
mLoadInfo->GetExternalContentPolicyType() !=
|
|
nsIContentPolicy::TYPE_SUBDOCUMENT) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsILoadInfo::CrossOriginEmbedderPolicy resultPolicy =
|
|
nsILoadInfo::EMBEDDER_POLICY_NULL;
|
|
rv = GetResponseEmbedderPolicy(&resultPolicy);
|
|
if (NS_FAILED(rv)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// https://mikewest.github.io/corpp/#abstract-opdef-process-navigation-response
|
|
if (mLoadInfo->GetExternalContentPolicyType() ==
|
|
nsIContentPolicy::TYPE_SUBDOCUMENT &&
|
|
mLoadInfo->GetLoadingEmbedderPolicy() !=
|
|
nsILoadInfo::EMBEDDER_POLICY_NULL &&
|
|
resultPolicy != nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP) {
|
|
return NS_ERROR_BLOCKED_BY_POLICY;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// https://mikewest.github.io/corpp/#corp-check
|
|
nsresult HttpBaseChannel::ProcessCrossOriginResourcePolicyHeader() {
|
|
// Fetch 4.5.9
|
|
uint32_t corsMode;
|
|
MOZ_ALWAYS_SUCCEEDS(GetCorsMode(&corsMode));
|
|
if (corsMode != nsIHttpChannelInternal::CORS_MODE_NO_CORS) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// We only apply this for resources.
|
|
if (mLoadInfo->GetExternalContentPolicyType() ==
|
|
nsIContentPolicy::TYPE_DOCUMENT ||
|
|
mLoadInfo->GetExternalContentPolicyType() ==
|
|
nsIContentPolicy::TYPE_WEBSOCKET) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (mLoadInfo->GetExternalContentPolicyType() ==
|
|
nsIContentPolicy::TYPE_SUBDOCUMENT) {
|
|
// COEP pref off, skip CORP checking for subdocument.
|
|
if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
|
|
return NS_OK;
|
|
}
|
|
// COEP 3.2.1.2 when request targets a nested browsing context then embedder
|
|
// policy value is "unsafe-none", then return allowed.
|
|
if (mLoadInfo->GetLoadingEmbedderPolicy() ==
|
|
nsILoadInfo::EMBEDDER_POLICY_NULL) {
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(mLoadInfo->GetLoadingPrincipal(),
|
|
"Resources should always have a LoadingPrincipal");
|
|
if (!mResponseHead) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsAutoCString content;
|
|
Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Resource_Policy,
|
|
content);
|
|
|
|
if (StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
|
|
// COEP 3.2.1.6 If policy is null, and embedder policy is "require-corp",
|
|
// set policy to "same-origin".
|
|
// Note that we treat invalid value as "cross-origin", which spec
|
|
// indicates. We might want to make that stricter.
|
|
if (content.IsEmpty() && mLoadInfo->GetLoadingEmbedderPolicy() ==
|
|
nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP) {
|
|
content = "same-origin"_ns;
|
|
}
|
|
}
|
|
|
|
if (content.IsEmpty()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> channelOrigin;
|
|
nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
|
|
this, getter_AddRefs(channelOrigin));
|
|
|
|
// Cross-Origin-Resource-Policy = %s"same-origin" / %s"same-site" /
|
|
// %s"cross-origin"
|
|
if (content.EqualsLiteral("same-origin")) {
|
|
if (!channelOrigin->Equals(mLoadInfo->GetLoadingPrincipal())) {
|
|
return NS_ERROR_DOM_CORP_FAILED;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
if (content.EqualsLiteral("same-site")) {
|
|
nsAutoCString documentBaseDomain;
|
|
nsAutoCString resourceBaseDomain;
|
|
mLoadInfo->GetLoadingPrincipal()->GetBaseDomain(documentBaseDomain);
|
|
channelOrigin->GetBaseDomain(resourceBaseDomain);
|
|
if (documentBaseDomain != resourceBaseDomain) {
|
|
return NS_ERROR_DOM_CORP_FAILED;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> resourceURI = channelOrigin->GetURI();
|
|
if (!mLoadInfo->GetLoadingPrincipal()->SchemeIs("https") &&
|
|
resourceURI->SchemeIs("https")) {
|
|
return NS_ERROR_DOM_CORP_FAILED;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// See https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e
|
|
// This method runs steps 1-4 of the algorithm to compare
|
|
// cross-origin-opener policies
|
|
static bool CompareCrossOriginOpenerPolicies(
|
|
nsILoadInfo::CrossOriginOpenerPolicy documentPolicy,
|
|
nsIPrincipal* documentOrigin,
|
|
nsILoadInfo::CrossOriginOpenerPolicy resultPolicy,
|
|
nsIPrincipal* resultOrigin) {
|
|
if (documentPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE &&
|
|
resultPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) {
|
|
return true;
|
|
}
|
|
|
|
if (documentPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE ||
|
|
resultPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) {
|
|
return false;
|
|
}
|
|
|
|
if (documentPolicy == resultPolicy && documentOrigin->Equals(resultOrigin)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// This runs steps 1-5 of the algorithm when navigating a top level document.
|
|
// See https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e
|
|
nsresult HttpBaseChannel::ComputeCrossOriginOpenerPolicyMismatch() {
|
|
mHasCrossOriginOpenerPolicyMismatch = false;
|
|
if (!StaticPrefs::browser_tabs_remote_useCrossOriginOpenerPolicy()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Only consider Cross-Origin-Opener-Policy for toplevel document loads.
|
|
if (mLoadInfo->GetExternalContentPolicyType() !=
|
|
nsIContentPolicy::TYPE_DOCUMENT) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Maybe the channel failed and we have no response head?
|
|
if (!mResponseHead) {
|
|
// Not having a response head is not a hard failure at the point where
|
|
// this method is called.
|
|
return NS_OK;
|
|
}
|
|
|
|
RefPtr<mozilla::dom::BrowsingContext> ctx;
|
|
mLoadInfo->GetBrowsingContext(getter_AddRefs(ctx));
|
|
|
|
// In xpcshell-tests we don't always have a browsingContext
|
|
if (!ctx) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Get the policy of the active document, and the policy for the result.
|
|
nsILoadInfo::CrossOriginOpenerPolicy documentPolicy = ctx->GetOpenerPolicy();
|
|
nsILoadInfo::CrossOriginOpenerPolicy resultPolicy =
|
|
nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
|
|
Unused << ComputeCrossOriginOpenerPolicy(documentPolicy, &resultPolicy);
|
|
mComputedCrossOriginOpenerPolicy = resultPolicy;
|
|
|
|
// If bc's popup sandboxing flag set is not empty and potentialCOOP is
|
|
// non-null, then navigate bc to a network error and abort these steps.
|
|
if (resultPolicy != nsILoadInfo::OPENER_POLICY_UNSAFE_NONE &&
|
|
GetHasNonEmptySandboxingFlag()) {
|
|
LOG((
|
|
"HttpBaseChannel::ComputeCrossOriginOpenerPolicyMismatch network error "
|
|
"for non empty sandboxing and non null COOP"));
|
|
return NS_ERROR_BLOCKED_BY_POLICY;
|
|
}
|
|
|
|
// In xpcshell-tests we don't always have a current window global
|
|
if (!ctx->Canonical()->GetCurrentWindowGlobal()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// We use the top window principal as the documentOrigin
|
|
nsCOMPtr<nsIPrincipal> documentOrigin =
|
|
ctx->Canonical()->GetCurrentWindowGlobal()->DocumentPrincipal();
|
|
nsCOMPtr<nsIPrincipal> resultOrigin;
|
|
|
|
nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
|
|
this, getter_AddRefs(resultOrigin));
|
|
|
|
bool compareResult = CompareCrossOriginOpenerPolicies(
|
|
documentPolicy, documentOrigin, resultPolicy, resultOrigin);
|
|
|
|
if (LOG_ENABLED()) {
|
|
LOG(
|
|
("HttpBaseChannel::HasCrossOriginOpenerPolicyMismatch - "
|
|
"doc:%d result:%d - compare:%d\n",
|
|
documentPolicy, resultPolicy, compareResult));
|
|
nsAutoCString docOrigin("(null)");
|
|
nsCOMPtr<nsIURI> uri = documentOrigin->GetURI();
|
|
if (uri) {
|
|
uri->GetSpec(docOrigin);
|
|
}
|
|
nsAutoCString resOrigin("(null)");
|
|
uri = resultOrigin->GetURI();
|
|
if (uri) {
|
|
uri->GetSpec(resOrigin);
|
|
}
|
|
LOG(("doc origin:%s - res origin: %s\n", docOrigin.get(), resOrigin.get()));
|
|
}
|
|
|
|
if (compareResult) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// If one of the following is false:
|
|
// - document's policy is same-origin-allow-popups
|
|
// - resultPolicy is null
|
|
// - doc is the initial about:blank document
|
|
// then we have a mismatch.
|
|
|
|
if (documentPolicy != nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS) {
|
|
mHasCrossOriginOpenerPolicyMismatch = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
if (resultPolicy != nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) {
|
|
mHasCrossOriginOpenerPolicyMismatch = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!ctx->Canonical()->GetCurrentWindowGlobal()->IsInitialDocument()) {
|
|
mHasCrossOriginOpenerPolicyMismatch = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetCookie(const nsACString& aCookieHeader) {
|
|
if (mLoadFlags & LOAD_ANONYMOUS) return NS_OK;
|
|
|
|
if (IsBrowsingContextDiscarded()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// empty header isn't an error
|
|
if (aCookieHeader.IsEmpty()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsICookieService* cs = gHttpHandler->GetCookieService();
|
|
NS_ENSURE_TRUE(cs, NS_ERROR_FAILURE);
|
|
|
|
nsresult rv = cs->SetCookieStringFromHttp(mURI, aCookieHeader, this);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
NotifySetCookie(aCookieHeader);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetThirdPartyFlags(uint32_t* aFlags) {
|
|
*aFlags = mThirdPartyFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetThirdPartyFlags(uint32_t aFlags) {
|
|
ENSURE_CALLED_BEFORE_ASYNC_OPEN();
|
|
|
|
mThirdPartyFlags = aFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetForceAllowThirdPartyCookie(bool* aForce) {
|
|
*aForce =
|
|
!!(mThirdPartyFlags & nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetForceAllowThirdPartyCookie(bool aForce) {
|
|
ENSURE_CALLED_BEFORE_ASYNC_OPEN();
|
|
|
|
if (aForce)
|
|
mThirdPartyFlags |= nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW;
|
|
else
|
|
mThirdPartyFlags &= ~nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetCanceled(bool* aCanceled) {
|
|
*aCanceled = mCanceled;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetChannelIsForDownload(bool* aChannelIsForDownload) {
|
|
*aChannelIsForDownload = mChannelIsForDownload;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetChannelIsForDownload(bool aChannelIsForDownload) {
|
|
mChannelIsForDownload = aChannelIsForDownload;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetCacheKeysRedirectChain(nsTArray<nsCString>* cacheKeys) {
|
|
mRedirectedCachekeys = WrapUnique(cacheKeys);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLocalAddress(nsACString& addr) {
|
|
if (mSelfAddr.raw.family == PR_AF_UNSPEC) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
addr.SetLength(kIPv6CStrBufSize);
|
|
NetAddrToString(&mSelfAddr, addr.BeginWriting(), kIPv6CStrBufSize);
|
|
addr.SetLength(strlen(addr.BeginReading()));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::TakeAllSecurityMessages(
|
|
nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
aMessages.Clear();
|
|
for (auto pair : mSecurityConsoleMessages) {
|
|
nsresult rv;
|
|
nsCOMPtr<nsISecurityConsoleMessage> message =
|
|
do_CreateInstance(NS_SECURITY_CONSOLE_MESSAGE_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
message->SetTag(pair.first);
|
|
message->SetCategory(pair.second);
|
|
aMessages.AppendElement(message);
|
|
}
|
|
|
|
MOZ_ASSERT(mSecurityConsoleMessages.Length() == aMessages.Length());
|
|
mSecurityConsoleMessages.Clear();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* Please use this method with care. This can cause the message
|
|
* queue to grow large and cause the channel to take up a lot
|
|
* of memory. Use only static string messages and do not add
|
|
* server side data to the queue, as that can be large.
|
|
* Add only a limited number of messages to the queue to keep
|
|
* the channel size down and do so only in rare erroneous situations.
|
|
* More information can be found here:
|
|
* https://bugzilla.mozilla.org/show_bug.cgi?id=846918
|
|
*/
|
|
nsresult HttpBaseChannel::AddSecurityMessage(
|
|
const nsAString& aMessageTag, const nsAString& aMessageCategory) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsresult rv;
|
|
|
|
// nsSecurityConsoleMessage is not thread-safe refcounted.
|
|
// Delay the object construction until requested.
|
|
// See TakeAllSecurityMessages()
|
|
std::pair<nsString, nsString> pair(aMessageTag, aMessageCategory);
|
|
mSecurityConsoleMessages.AppendElement(std::move(pair));
|
|
|
|
nsCOMPtr<nsIConsoleService> console(
|
|
do_GetService(NS_CONSOLESERVICE_CONTRACTID));
|
|
if (!console) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<nsILoadInfo> loadInfo = LoadInfo();
|
|
|
|
auto innerWindowID = loadInfo->GetInnerWindowID();
|
|
|
|
nsAutoString errorText;
|
|
rv = nsContentUtils::GetLocalizedString(
|
|
nsContentUtils::eSECURITY_PROPERTIES,
|
|
NS_ConvertUTF16toUTF8(aMessageTag).get(), errorText);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
|
|
error->InitWithSourceURI(
|
|
errorText, mURI, EmptyString(), 0, 0, nsIScriptError::warningFlag,
|
|
NS_ConvertUTF16toUTF8(aMessageCategory), innerWindowID);
|
|
|
|
console->LogMessage(error);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLocalPort(int32_t* port) {
|
|
NS_ENSURE_ARG_POINTER(port);
|
|
|
|
if (mSelfAddr.raw.family == PR_AF_INET) {
|
|
*port = (int32_t)ntohs(mSelfAddr.inet.port);
|
|
} else if (mSelfAddr.raw.family == PR_AF_INET6) {
|
|
*port = (int32_t)ntohs(mSelfAddr.inet6.port);
|
|
} else
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRemoteAddress(nsACString& addr) {
|
|
if (mPeerAddr.raw.family == PR_AF_UNSPEC) return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
addr.SetLength(kIPv6CStrBufSize);
|
|
NetAddrToString(&mPeerAddr, addr.BeginWriting(), kIPv6CStrBufSize);
|
|
addr.SetLength(strlen(addr.BeginReading()));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRemotePort(int32_t* port) {
|
|
NS_ENSURE_ARG_POINTER(port);
|
|
|
|
if (mPeerAddr.raw.family == PR_AF_INET) {
|
|
*port = (int32_t)ntohs(mPeerAddr.inet.port);
|
|
} else if (mPeerAddr.raw.family == PR_AF_INET6) {
|
|
*port = (int32_t)ntohs(mPeerAddr.inet6.port);
|
|
} else
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::HTTPUpgrade(const nsACString& aProtocolName,
|
|
nsIHttpUpgradeListener* aListener) {
|
|
NS_ENSURE_ARG(!aProtocolName.IsEmpty());
|
|
NS_ENSURE_ARG_POINTER(aListener);
|
|
|
|
mUpgradeProtocol = aProtocolName;
|
|
mUpgradeProtocolCallback = aListener;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetOnlyConnect(bool* aOnlyConnect) {
|
|
NS_ENSURE_ARG_POINTER(aOnlyConnect);
|
|
|
|
*aOnlyConnect = mCaps & NS_HTTP_CONNECT_ONLY;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetConnectOnly() {
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
|
|
if (!mUpgradeProtocolCallback) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mCaps |= NS_HTTP_CONNECT_ONLY;
|
|
mProxyResolveFlags = nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
|
|
nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL;
|
|
return SetLoadFlags(nsIRequest::INHIBIT_CACHING | nsIChannel::LOAD_ANONYMOUS |
|
|
nsIRequest::LOAD_BYPASS_CACHE |
|
|
nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetAllowSpdy(bool* aAllowSpdy) {
|
|
NS_ENSURE_ARG_POINTER(aAllowSpdy);
|
|
|
|
*aAllowSpdy = mAllowSpdy;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetAllowSpdy(bool aAllowSpdy) {
|
|
mAllowSpdy = aAllowSpdy;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetAllowAltSvc(bool* aAllowAltSvc) {
|
|
NS_ENSURE_ARG_POINTER(aAllowAltSvc);
|
|
|
|
*aAllowAltSvc = mAllowAltSvc;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetAllowAltSvc(bool aAllowAltSvc) {
|
|
mAllowAltSvc = aAllowAltSvc;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetBeConservative(bool* aBeConservative) {
|
|
NS_ENSURE_ARG_POINTER(aBeConservative);
|
|
|
|
*aBeConservative = mBeConservative;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetBeConservative(bool aBeConservative) {
|
|
mBeConservative = aBeConservative;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetIsTRRServiceChannel(bool* aIsTRRServiceChannel) {
|
|
NS_ENSURE_ARG_POINTER(aIsTRRServiceChannel);
|
|
|
|
*aIsTRRServiceChannel = mIsTRRServiceChannel;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetIsTRRServiceChannel(bool aIsTRRServiceChannel) {
|
|
mIsTRRServiceChannel = aIsTRRServiceChannel;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetIsResolvedByTRR(bool* aResolvedByTRR) {
|
|
NS_ENSURE_ARG_POINTER(aResolvedByTRR);
|
|
*aResolvedByTRR = mResolvedByTRR;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetTlsFlags(uint32_t* aTlsFlags) {
|
|
NS_ENSURE_ARG_POINTER(aTlsFlags);
|
|
|
|
*aTlsFlags = mTlsFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetTlsFlags(uint32_t aTlsFlags) {
|
|
mTlsFlags = aTlsFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetApiRedirectToURI(nsIURI** aResult) {
|
|
NS_ENSURE_ARG_POINTER(aResult);
|
|
NS_IF_ADDREF(*aResult = mAPIRedirectToURI);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetResponseTimeoutEnabled(bool* aEnable) {
|
|
if (NS_WARN_IF(!aEnable)) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
*aEnable = mResponseTimeoutEnabled;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetResponseTimeoutEnabled(bool aEnable) {
|
|
mResponseTimeoutEnabled = aEnable;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetInitialRwin(uint32_t* aRwin) {
|
|
if (NS_WARN_IF(!aRwin)) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
*aRwin = mInitialRwin;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetInitialRwin(uint32_t aRwin) {
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
mInitialRwin = aRwin;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::ForcePending(bool aForcePending) {
|
|
mForcePending = aForcePending;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLastModifiedTime(PRTime* lastModifiedTime) {
|
|
if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
|
|
uint32_t lastMod;
|
|
nsresult rv = mResponseHead->GetLastModifiedValue(&lastMod);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
*lastModifiedTime = lastMod;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetCorsIncludeCredentials(bool* aInclude) {
|
|
*aInclude = mCorsIncludeCredentials;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetCorsIncludeCredentials(bool aInclude) {
|
|
mCorsIncludeCredentials = aInclude;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetCorsMode(uint32_t* aMode) {
|
|
*aMode = mCorsMode;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetCorsMode(uint32_t aMode) {
|
|
mCorsMode = aMode;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRedirectMode(uint32_t* aMode) {
|
|
*aMode = mRedirectMode;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetRedirectMode(uint32_t aMode) {
|
|
mRedirectMode = aMode;
|
|
return NS_OK;
|
|
}
|
|
|
|
namespace {
|
|
|
|
bool ContainsAllFlags(uint32_t aLoadFlags, uint32_t aMask) {
|
|
return (aLoadFlags & aMask) == aMask;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetFetchCacheMode(uint32_t* aFetchCacheMode) {
|
|
NS_ENSURE_ARG_POINTER(aFetchCacheMode);
|
|
|
|
// Otherwise try to guess an appropriate cache mode from the load flags.
|
|
if (ContainsAllFlags(mLoadFlags, INHIBIT_CACHING | LOAD_BYPASS_CACHE)) {
|
|
*aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE;
|
|
} else if (ContainsAllFlags(mLoadFlags, LOAD_BYPASS_CACHE)) {
|
|
*aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD;
|
|
} else if (ContainsAllFlags(mLoadFlags, VALIDATE_ALWAYS)) {
|
|
*aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE;
|
|
} else if (ContainsAllFlags(
|
|
mLoadFlags,
|
|
VALIDATE_NEVER | nsICachingChannel::LOAD_ONLY_FROM_CACHE)) {
|
|
*aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED;
|
|
} else if (ContainsAllFlags(mLoadFlags, VALIDATE_NEVER)) {
|
|
*aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE;
|
|
} else {
|
|
*aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
namespace {
|
|
|
|
void SetCacheFlags(uint32_t& aLoadFlags, uint32_t aFlags) {
|
|
// First, clear any possible cache related flags.
|
|
uint32_t allPossibleFlags =
|
|
nsIRequest::INHIBIT_CACHING | nsIRequest::LOAD_BYPASS_CACHE |
|
|
nsIRequest::VALIDATE_ALWAYS | nsIRequest::LOAD_FROM_CACHE |
|
|
nsICachingChannel::LOAD_ONLY_FROM_CACHE;
|
|
aLoadFlags &= ~allPossibleFlags;
|
|
|
|
// Then set the new flags.
|
|
aLoadFlags |= aFlags;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetFetchCacheMode(uint32_t aFetchCacheMode) {
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
|
|
// Now, set the load flags that implement each cache mode.
|
|
switch (aFetchCacheMode) {
|
|
case nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT:
|
|
// The "default" mode means to use the http cache normally and
|
|
// respect any http cache-control headers. We effectively want
|
|
// to clear our cache related load flags.
|
|
SetCacheFlags(mLoadFlags, 0);
|
|
break;
|
|
case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE:
|
|
// no-store means don't consult the cache on the way to the network, and
|
|
// don't store the response in the cache even if it's cacheable.
|
|
SetCacheFlags(mLoadFlags, INHIBIT_CACHING | LOAD_BYPASS_CACHE);
|
|
break;
|
|
case nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD:
|
|
// reload means don't consult the cache on the way to the network, but
|
|
// do store the response in the cache if possible.
|
|
SetCacheFlags(mLoadFlags, LOAD_BYPASS_CACHE);
|
|
break;
|
|
case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE:
|
|
// no-cache means always validate what's in the cache.
|
|
SetCacheFlags(mLoadFlags, VALIDATE_ALWAYS);
|
|
break;
|
|
case nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE:
|
|
// force-cache means don't validate unless if the response would vary.
|
|
SetCacheFlags(mLoadFlags, VALIDATE_NEVER);
|
|
break;
|
|
case nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED:
|
|
// only-if-cached means only from cache, no network, no validation,
|
|
// generate a network error if the document was't in the cache. The
|
|
// privacy implications of these flags (making it fast/easy to check if
|
|
// the user has things in their cache without any network traffic side
|
|
// effects) are addressed in the Request constructor which
|
|
// enforces/requires same-origin request mode.
|
|
SetCacheFlags(mLoadFlags,
|
|
VALIDATE_NEVER | nsICachingChannel::LOAD_ONLY_FROM_CACHE);
|
|
break;
|
|
}
|
|
|
|
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
|
uint32_t finalMode = 0;
|
|
MOZ_ALWAYS_SUCCEEDS(GetFetchCacheMode(&finalMode));
|
|
MOZ_DIAGNOSTIC_ASSERT(finalMode == aFetchCacheMode);
|
|
#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetIntegrityMetadata(const nsAString& aIntegrityMetadata) {
|
|
mIntegrityMetadata = aIntegrityMetadata;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetIntegrityMetadata(nsAString& aIntegrityMetadata) {
|
|
aIntegrityMetadata = mIntegrityMetadata;
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsISupportsPriority
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetPriority(int32_t* value) {
|
|
*value = mPriority;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::AdjustPriority(int32_t delta) {
|
|
return SetPriority(mPriority + delta);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIResumableChannel
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetEntityID(nsACString& aEntityID) {
|
|
// Don't return an entity ID for Non-GET requests which require
|
|
// additional data
|
|
if (!mRequestHead.IsGet()) {
|
|
return NS_ERROR_NOT_RESUMABLE;
|
|
}
|
|
|
|
uint64_t size = UINT64_MAX;
|
|
nsAutoCString etag, lastmod;
|
|
if (mResponseHead) {
|
|
// Don't return an entity if the server sent the following header:
|
|
// Accept-Ranges: none
|
|
// Not sending the Accept-Ranges header means we can still try
|
|
// sending range requests.
|
|
nsAutoCString acceptRanges;
|
|
Unused << mResponseHead->GetHeader(nsHttp::Accept_Ranges, acceptRanges);
|
|
if (!acceptRanges.IsEmpty() &&
|
|
!nsHttp::FindToken(acceptRanges.get(), "bytes",
|
|
HTTP_HEADER_VALUE_SEPS)) {
|
|
return NS_ERROR_NOT_RESUMABLE;
|
|
}
|
|
|
|
size = mResponseHead->TotalEntitySize();
|
|
Unused << mResponseHead->GetHeader(nsHttp::Last_Modified, lastmod);
|
|
Unused << mResponseHead->GetHeader(nsHttp::ETag, etag);
|
|
}
|
|
nsCString entityID;
|
|
NS_EscapeURL(etag.BeginReading(), etag.Length(),
|
|
esc_AlwaysCopy | esc_FileBaseName | esc_Forced, entityID);
|
|
entityID.Append('/');
|
|
entityID.AppendInt(int64_t(size));
|
|
entityID.Append('/');
|
|
entityID.Append(lastmod);
|
|
// NOTE: Appending lastmod as the last part avoids having to escape it
|
|
|
|
aEntityID = entityID;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIConsoleReportCollector
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void HttpBaseChannel::AddConsoleReport(
|
|
uint32_t aErrorFlags, const nsACString& aCategory,
|
|
nsContentUtils::PropertiesFile aPropertiesFile,
|
|
const nsACString& aSourceFileURI, uint32_t aLineNumber,
|
|
uint32_t aColumnNumber, const nsACString& aMessageName,
|
|
const nsTArray<nsString>& aStringParams) {
|
|
mReportCollector->AddConsoleReport(aErrorFlags, aCategory, aPropertiesFile,
|
|
aSourceFileURI, aLineNumber, aColumnNumber,
|
|
aMessageName, aStringParams);
|
|
|
|
// If this channel is already part of a loadGroup, we can flush this console
|
|
// report immediately.
|
|
HttpBaseChannel::MaybeFlushConsoleReports();
|
|
}
|
|
|
|
void HttpBaseChannel::FlushReportsToConsole(uint64_t aInnerWindowID,
|
|
ReportAction aAction) {
|
|
mReportCollector->FlushReportsToConsole(aInnerWindowID, aAction);
|
|
}
|
|
|
|
void HttpBaseChannel::FlushReportsToConsoleForServiceWorkerScope(
|
|
const nsACString& aScope, ReportAction aAction) {
|
|
mReportCollector->FlushReportsToConsoleForServiceWorkerScope(aScope, aAction);
|
|
}
|
|
|
|
void HttpBaseChannel::FlushConsoleReports(dom::Document* aDocument,
|
|
ReportAction aAction) {
|
|
mReportCollector->FlushConsoleReports(aDocument, aAction);
|
|
}
|
|
|
|
void HttpBaseChannel::FlushConsoleReports(nsILoadGroup* aLoadGroup,
|
|
ReportAction aAction) {
|
|
mReportCollector->FlushConsoleReports(aLoadGroup, aAction);
|
|
}
|
|
|
|
void HttpBaseChannel::FlushConsoleReports(
|
|
nsIConsoleReportCollector* aCollector) {
|
|
mReportCollector->FlushConsoleReports(aCollector);
|
|
}
|
|
|
|
void HttpBaseChannel::StealConsoleReports(
|
|
nsTArray<net::ConsoleReportCollected>& aReports) {
|
|
mReportCollector->StealConsoleReports(aReports);
|
|
}
|
|
|
|
void HttpBaseChannel::ClearConsoleReports() {
|
|
mReportCollector->ClearConsoleReports();
|
|
}
|
|
|
|
nsIPrincipal* HttpBaseChannel::GetURIPrincipal() {
|
|
if (mPrincipal) {
|
|
return mPrincipal;
|
|
}
|
|
|
|
nsIScriptSecurityManager* securityManager =
|
|
nsContentUtils::GetSecurityManager();
|
|
|
|
if (!securityManager) {
|
|
LOG(("HttpBaseChannel::GetURIPrincipal: No security manager [this=%p]",
|
|
this));
|
|
return nullptr;
|
|
}
|
|
|
|
securityManager->GetChannelURIPrincipal(this, getter_AddRefs(mPrincipal));
|
|
if (!mPrincipal) {
|
|
LOG(("HttpBaseChannel::GetURIPrincipal: No channel principal [this=%p]",
|
|
this));
|
|
return nullptr;
|
|
}
|
|
|
|
return mPrincipal;
|
|
}
|
|
|
|
bool HttpBaseChannel::IsNavigation() {
|
|
return mForceMainDocumentChannel || (mLoadFlags & LOAD_DOCUMENT_URI);
|
|
}
|
|
|
|
bool HttpBaseChannel::BypassServiceWorker() const {
|
|
return mLoadFlags & LOAD_BYPASS_SERVICE_WORKER;
|
|
}
|
|
|
|
bool HttpBaseChannel::ShouldIntercept(nsIURI* aURI) {
|
|
nsCOMPtr<nsINetworkInterceptController> controller;
|
|
GetCallback(controller);
|
|
bool shouldIntercept = false;
|
|
|
|
// We should never intercept internal redirects. The ServiceWorker code
|
|
// can trigger interntal redirects as the result of a FetchEvent. If
|
|
// we re-intercept then an infinite loop can occur.
|
|
//
|
|
// Its also important that we do not set the LOAD_BYPASS_SERVICE_WORKER
|
|
// flag because an internal redirect occurs. Its possible that another
|
|
// interception should occur after the internal redirect. For example,
|
|
// if the ServiceWorker chooses not to call respondWith() the channel
|
|
// will be reset with an internal redirect. If the request is a navigation
|
|
// and the network then triggers a redirect its possible the new URL
|
|
// should be intercepted again.
|
|
//
|
|
// Note, HSTS upgrade redirects are often treated the same as internal
|
|
// redirects. In this case, however, we intentionally allow interception
|
|
// of HSTS upgrade redirects. This matches the expected spec behavior and
|
|
// does not run the risk of infinite loops as described above.
|
|
bool internalRedirect =
|
|
mLastRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL;
|
|
|
|
if (controller && mLoadInfo && !BypassServiceWorker() && !internalRedirect) {
|
|
nsresult rv = controller->ShouldPrepareForIntercept(
|
|
aURI ? aURI : mURI.get(), this, &shouldIntercept);
|
|
if (NS_FAILED(rv)) {
|
|
return false;
|
|
}
|
|
}
|
|
return shouldIntercept;
|
|
}
|
|
|
|
void HttpBaseChannel::AddAsNonTailRequest() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (EnsureRequestContext()) {
|
|
LOG((
|
|
"HttpBaseChannel::AddAsNonTailRequest this=%p, rc=%p, already added=%d",
|
|
this, mRequestContext.get(), (bool)mAddedAsNonTailRequest));
|
|
|
|
if (!mAddedAsNonTailRequest) {
|
|
mRequestContext->AddNonTailRequest();
|
|
mAddedAsNonTailRequest = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void HttpBaseChannel::RemoveAsNonTailRequest() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mRequestContext) {
|
|
LOG(
|
|
("HttpBaseChannel::RemoveAsNonTailRequest this=%p, rc=%p, already "
|
|
"added=%d",
|
|
this, mRequestContext.get(), (bool)mAddedAsNonTailRequest));
|
|
|
|
if (mAddedAsNonTailRequest) {
|
|
mRequestContext->RemoveNonTailRequest();
|
|
mAddedAsNonTailRequest = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void HttpBaseChannel::AssertPrivateBrowsingId() {
|
|
nsCOMPtr<nsILoadContext> loadContext;
|
|
NS_QueryNotificationCallbacks(this, loadContext);
|
|
|
|
if (!loadContext) {
|
|
return;
|
|
}
|
|
|
|
// We skip testing of favicon loading here since it could be triggered by XUL
|
|
// image which uses SystemPrincipal. The SystemPrincpal doesn't have
|
|
// mPrivateBrowsingId.
|
|
if (mLoadInfo->GetLoadingPrincipal() &&
|
|
mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal() &&
|
|
mLoadInfo->InternalContentPolicyType() ==
|
|
nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
|
|
return;
|
|
}
|
|
|
|
OriginAttributes docShellAttrs;
|
|
loadContext->GetOriginAttributes(docShellAttrs);
|
|
MOZ_ASSERT(mLoadInfo->GetOriginAttributes().mPrivateBrowsingId ==
|
|
docShellAttrs.mPrivateBrowsingId,
|
|
"PrivateBrowsingId values are not the same between LoadInfo and "
|
|
"LoadContext.");
|
|
}
|
|
#endif
|
|
|
|
already_AddRefed<nsILoadInfo> HttpBaseChannel::CloneLoadInfoForRedirect(
|
|
nsIURI* aNewURI, uint32_t aRedirectFlags) {
|
|
// make a copy of the loadinfo, append to the redirectchain
|
|
// this will be set on the newly created channel for the redirect target.
|
|
nsCOMPtr<nsILoadInfo> newLoadInfo =
|
|
static_cast<mozilla::net::LoadInfo*>(mLoadInfo.get())->Clone();
|
|
|
|
nsContentPolicyType contentPolicyType =
|
|
mLoadInfo->GetExternalContentPolicyType();
|
|
if (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT ||
|
|
contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT) {
|
|
nsCOMPtr<nsIPrincipal> nullPrincipalToInherit =
|
|
NullPrincipal::CreateWithoutOriginAttributes();
|
|
newLoadInfo->SetPrincipalToInherit(nullPrincipalToInherit);
|
|
}
|
|
|
|
bool isTopLevelDoc = newLoadInfo->GetExternalContentPolicyType() ==
|
|
nsIContentPolicy::TYPE_DOCUMENT;
|
|
|
|
if (isTopLevelDoc) {
|
|
// re-compute the origin attributes of the loadInfo if it's top-level load.
|
|
nsCOMPtr<nsILoadContext> loadContext;
|
|
NS_QueryNotificationCallbacks(this, loadContext);
|
|
OriginAttributes docShellAttrs;
|
|
if (loadContext) {
|
|
loadContext->GetOriginAttributes(docShellAttrs);
|
|
}
|
|
|
|
OriginAttributes attrs = newLoadInfo->GetOriginAttributes();
|
|
|
|
MOZ_ASSERT(
|
|
docShellAttrs.mUserContextId == attrs.mUserContextId,
|
|
"docshell and necko should have the same userContextId attribute.");
|
|
MOZ_ASSERT(
|
|
docShellAttrs.mPrivateBrowsingId == attrs.mPrivateBrowsingId,
|
|
"docshell and necko should have the same privateBrowsingId attribute.");
|
|
MOZ_ASSERT(docShellAttrs.mGeckoViewSessionContextId ==
|
|
attrs.mGeckoViewSessionContextId,
|
|
"docshell and necko should have the same "
|
|
"geckoViewSessionContextId attribute");
|
|
|
|
attrs = docShellAttrs;
|
|
attrs.SetFirstPartyDomain(true, aNewURI);
|
|
newLoadInfo->SetOriginAttributes(attrs);
|
|
|
|
// re-compute the upgrade insecure requests bit for document navigations
|
|
// since it should only apply to same-origin navigations (redirects).
|
|
// we only do this if the CSP of the triggering element (the cspToInherit)
|
|
// uses 'upgrade-insecure-requests', otherwise UIR does not apply.
|
|
nsCOMPtr<nsIContentSecurityPolicy> csp = newLoadInfo->GetCspToInherit();
|
|
if (csp) {
|
|
bool upgradeInsecureRequests = false;
|
|
csp->GetUpgradeInsecureRequests(&upgradeInsecureRequests);
|
|
if (upgradeInsecureRequests) {
|
|
nsCOMPtr<nsIPrincipal> resultPrincipal =
|
|
BasePrincipal::CreateContentPrincipal(
|
|
aNewURI, newLoadInfo->GetOriginAttributes());
|
|
bool isConsideredSameOriginforUIR =
|
|
nsContentSecurityUtils::IsConsideredSameOriginForUIR(
|
|
newLoadInfo->TriggeringPrincipal(), resultPrincipal);
|
|
static_cast<mozilla::net::LoadInfo*>(newLoadInfo.get())
|
|
->SetUpgradeInsecureRequests(isConsideredSameOriginforUIR);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Leave empty, we want a 'clean ground' when creating the new channel.
|
|
// This will be ensured to be either set by the protocol handler or set
|
|
// to the redirect target URI properly after the channel creation.
|
|
newLoadInfo->SetResultPrincipalURI(nullptr);
|
|
|
|
bool isInternalRedirect =
|
|
(aRedirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
|
|
nsIChannelEventSink::REDIRECT_STS_UPGRADE));
|
|
|
|
nsCString remoteAddress;
|
|
Unused << GetRemoteAddress(remoteAddress);
|
|
nsCOMPtr<nsIURI> referrer;
|
|
if (mReferrerInfo) {
|
|
referrer = mReferrerInfo->GetComputedReferrer();
|
|
}
|
|
|
|
nsCOMPtr<nsIRedirectHistoryEntry> entry =
|
|
new nsRedirectHistoryEntry(GetURIPrincipal(), referrer, remoteAddress);
|
|
|
|
newLoadInfo->AppendRedirectHistoryEntry(entry, isInternalRedirect);
|
|
|
|
return newLoadInfo.forget();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nsHttpChannel::nsITraceableChannel
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetNewListener(nsIStreamListener* aListener,
|
|
nsIStreamListener** _retval) {
|
|
LOG((
|
|
"HttpBaseChannel::SetNewListener [this=%p, mListener=%p, newListener=%p]",
|
|
this, mListener.get(), aListener));
|
|
|
|
if (!mTracingEnabled) return NS_ERROR_FAILURE;
|
|
|
|
NS_ENSURE_STATE(mListener);
|
|
NS_ENSURE_ARG_POINTER(aListener);
|
|
|
|
nsCOMPtr<nsIStreamListener> wrapper = new nsStreamListenerWrapper(mListener);
|
|
|
|
wrapper.forget(_retval);
|
|
mListener = aListener;
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel helpers
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void HttpBaseChannel::ReleaseListeners() {
|
|
MOZ_ASSERT(mCurrentThread->IsOnCurrentThread(),
|
|
"Should only be called on the current thread");
|
|
|
|
mListener = nullptr;
|
|
mCallbacks = nullptr;
|
|
mProgressSink = nullptr;
|
|
mCompressListener = nullptr;
|
|
}
|
|
|
|
void HttpBaseChannel::DoNotifyListener() {
|
|
LOG(("HttpBaseChannel::DoNotifyListener this=%p", this));
|
|
|
|
// In case nsHttpChannel::OnStartRequest wasn't called (e.g. due to flag
|
|
// LOAD_ONLY_IF_MODIFIED) we want to set mAfterOnStartRequestBegun to true
|
|
// before notifying listener.
|
|
if (!mAfterOnStartRequestBegun) {
|
|
mAfterOnStartRequestBegun = true;
|
|
}
|
|
|
|
if (mListener && !mOnStartRequestCalled) {
|
|
nsCOMPtr<nsIStreamListener> listener = mListener;
|
|
mOnStartRequestCalled = true;
|
|
listener->OnStartRequest(this);
|
|
}
|
|
mOnStartRequestCalled = true;
|
|
|
|
// Make sure mIsPending is set to false. At this moment we are done from
|
|
// the point of view of our consumer and we have to report our self
|
|
// as not-pending.
|
|
mIsPending = false;
|
|
|
|
if (mListener && !mOnStopRequestCalled) {
|
|
nsCOMPtr<nsIStreamListener> listener = mListener;
|
|
mOnStopRequestCalled = true;
|
|
listener->OnStopRequest(this, mStatus);
|
|
}
|
|
mOnStopRequestCalled = true;
|
|
|
|
// notify "http-on-stop-connect" observers
|
|
gHttpHandler->OnStopRequest(this);
|
|
|
|
// This channel has finished its job, potentially release any tail-blocked
|
|
// requests with this.
|
|
RemoveAsNonTailRequest();
|
|
|
|
// We have to make sure to drop the references to listeners and callbacks
|
|
// no longer needed.
|
|
ReleaseListeners();
|
|
|
|
DoNotifyListenerCleanup();
|
|
|
|
// If this is a navigation, then we must let the docshell flush the reports
|
|
// to the console later. The LoadDocument() is pointing at the detached
|
|
// document that started the navigation. We want to show the reports on the
|
|
// new document. Otherwise the console is wiped and the user never sees
|
|
// the information.
|
|
if (!IsNavigation()) {
|
|
if (mLoadGroup) {
|
|
FlushConsoleReports(mLoadGroup);
|
|
} else {
|
|
RefPtr<dom::Document> doc;
|
|
mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
|
|
FlushConsoleReports(doc);
|
|
}
|
|
}
|
|
}
|
|
|
|
void HttpBaseChannel::AddCookiesToRequest() {
|
|
if (mLoadFlags & LOAD_ANONYMOUS) {
|
|
return;
|
|
}
|
|
|
|
bool useCookieService = (XRE_IsParentProcess());
|
|
nsAutoCString cookie;
|
|
if (useCookieService) {
|
|
nsICookieService* cs = gHttpHandler->GetCookieService();
|
|
if (cs) {
|
|
cs->GetCookieStringFromHttp(mURI, this, cookie);
|
|
}
|
|
|
|
if (cookie.IsEmpty()) {
|
|
cookie = mUserSetCookieHeader;
|
|
} else if (!mUserSetCookieHeader.IsEmpty()) {
|
|
cookie.AppendLiteral("; ");
|
|
cookie.Append(mUserSetCookieHeader);
|
|
}
|
|
} else {
|
|
cookie = mUserSetCookieHeader;
|
|
}
|
|
|
|
// If we are in the child process, we want the parent seeing any
|
|
// cookie headers that might have been set by SetRequestHeader()
|
|
SetRequestHeader(nsDependentCString(nsHttp::Cookie), cookie, false);
|
|
}
|
|
|
|
/* static */
|
|
void HttpBaseChannel::PropagateReferenceIfNeeded(
|
|
nsIURI* aURI, nsCOMPtr<nsIURI>& aRedirectURI) {
|
|
bool hasRef = false;
|
|
nsresult rv = aRedirectURI->GetHasRef(&hasRef);
|
|
if (NS_SUCCEEDED(rv) && !hasRef) {
|
|
nsAutoCString ref;
|
|
aURI->GetRef(ref);
|
|
if (!ref.IsEmpty()) {
|
|
// NOTE: SetRef will fail if mRedirectURI is immutable
|
|
// (e.g. an about: URI)... Oh well.
|
|
Unused << NS_MutateURI(aRedirectURI).SetRef(ref).Finalize(aRedirectURI);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool HttpBaseChannel::ShouldRewriteRedirectToGET(
|
|
uint32_t httpStatus, nsHttpRequestHead::ParsedMethodType method) {
|
|
// for 301 and 302, only rewrite POST
|
|
if (httpStatus == 301 || httpStatus == 302)
|
|
return method == nsHttpRequestHead::kMethod_Post;
|
|
|
|
// rewrite for 303 unless it was HEAD
|
|
if (httpStatus == 303) return method != nsHttpRequestHead::kMethod_Head;
|
|
|
|
// otherwise, such as for 307, do not rewrite
|
|
return false;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::ShouldStripRequestBodyHeader(const nsACString& aMethod,
|
|
bool* aResult) {
|
|
*aResult = false;
|
|
uint32_t httpStatus = 0;
|
|
if (NS_FAILED(GetResponseStatus(&httpStatus))) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsAutoCString method(aMethod);
|
|
nsHttpRequestHead::ParsedMethodType parsedMethod;
|
|
nsHttpRequestHead::ParseMethod(method, parsedMethod);
|
|
// Fetch 4.4.11, which is slightly different than the perserved method
|
|
// algrorithm: strip request-body-header for GET->GET redirection for 303.
|
|
*aResult =
|
|
ShouldRewriteRedirectToGET(httpStatus, parsedMethod) &&
|
|
!(httpStatus == 303 && parsedMethod == nsHttpRequestHead::kMethod_Get);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
HttpBaseChannel::ReplacementChannelConfig
|
|
HttpBaseChannel::CloneReplacementChannelConfig(bool aPreserveMethod,
|
|
uint32_t aRedirectFlags,
|
|
ReplacementReason aReason) {
|
|
ReplacementChannelConfig config;
|
|
config.redirectFlags = aRedirectFlags;
|
|
config.classOfService = mClassOfService;
|
|
|
|
if (mPrivateBrowsingOverriden) {
|
|
config.privateBrowsing = Some(mPrivateBrowsing);
|
|
}
|
|
|
|
if (mReferrerInfo) {
|
|
// When cloning for a document channel replacement (parent process
|
|
// copying values for a new content process channel), this happens after
|
|
// OnStartRequest so we have the headers for the response available.
|
|
// We don't want to apply them to the referrer for the channel though,
|
|
// since that is the referrer for the current document, and the header
|
|
// should only apply to navigations from the current document.
|
|
if (aReason == ReplacementReason::DocumentChannel) {
|
|
config.referrerInfo = mReferrerInfo;
|
|
} else {
|
|
dom::ReferrerPolicy referrerPolicy = dom::ReferrerPolicy::_empty;
|
|
nsAutoCString tRPHeaderCValue;
|
|
Unused << GetResponseHeader("referrer-policy"_ns, tRPHeaderCValue);
|
|
NS_ConvertUTF8toUTF16 tRPHeaderValue(tRPHeaderCValue);
|
|
|
|
if (!tRPHeaderValue.IsEmpty()) {
|
|
referrerPolicy =
|
|
dom::ReferrerInfo::ReferrerPolicyFromHeaderString(tRPHeaderValue);
|
|
}
|
|
|
|
if (referrerPolicy != dom::ReferrerPolicy::_empty) {
|
|
// We may reuse computed referrer in redirect, so if referrerPolicy
|
|
// changes, we must not use the old computed value, and have to compute
|
|
// again.
|
|
nsCOMPtr<nsIReferrerInfo> referrerInfo =
|
|
dom::ReferrerInfo::CreateFromOtherAndPolicyOverride(mReferrerInfo,
|
|
referrerPolicy);
|
|
config.referrerInfo = referrerInfo;
|
|
} else {
|
|
config.referrerInfo = mReferrerInfo;
|
|
}
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsITimedChannel> oldTimedChannel(
|
|
do_QueryInterface(static_cast<nsIHttpChannel*>(this)));
|
|
if (oldTimedChannel) {
|
|
config.timedChannel = Some(dom::TimedChannelInfo());
|
|
config.timedChannel->timingEnabled() = mTimingEnabled;
|
|
config.timedChannel->redirectCount() = mRedirectCount;
|
|
config.timedChannel->internalRedirectCount() = mInternalRedirectCount;
|
|
config.timedChannel->asyncOpen() = mAsyncOpenTime;
|
|
config.timedChannel->channelCreation() = mChannelCreationTimestamp;
|
|
config.timedChannel->redirectStart() = mRedirectStartTimeStamp;
|
|
config.timedChannel->redirectEnd() = mRedirectEndTimeStamp;
|
|
config.timedChannel->initiatorType() = mInitiatorType;
|
|
config.timedChannel->allRedirectsSameOrigin() = mAllRedirectsSameOrigin;
|
|
config.timedChannel->allRedirectsPassTimingAllowCheck() =
|
|
mAllRedirectsPassTimingAllowCheck;
|
|
// Execute the timing allow check to determine whether
|
|
// to report the redirect timing info
|
|
nsCOMPtr<nsILoadInfo> loadInfo = LoadInfo();
|
|
// TYPE_DOCUMENT loads don't have a loadingPrincipal, so we can't set
|
|
// AllRedirectsPassTimingAllowCheck on them.
|
|
if (loadInfo->GetExternalContentPolicyType() !=
|
|
nsIContentPolicy::TYPE_DOCUMENT) {
|
|
nsCOMPtr<nsIPrincipal> principal = loadInfo->GetLoadingPrincipal();
|
|
config.timedChannel->timingAllowCheckForPrincipal() =
|
|
Some(oldTimedChannel->TimingAllowCheck(principal));
|
|
}
|
|
|
|
config.timedChannel->allRedirectsPassTimingAllowCheck() =
|
|
mAllRedirectsPassTimingAllowCheck;
|
|
config.timedChannel->launchServiceWorkerStart() = mLaunchServiceWorkerStart;
|
|
config.timedChannel->launchServiceWorkerEnd() = mLaunchServiceWorkerEnd;
|
|
config.timedChannel->dispatchFetchEventStart() = mDispatchFetchEventStart;
|
|
config.timedChannel->dispatchFetchEventEnd() = mDispatchFetchEventEnd;
|
|
config.timedChannel->handleFetchEventStart() = mHandleFetchEventStart;
|
|
config.timedChannel->handleFetchEventEnd() = mHandleFetchEventEnd;
|
|
config.timedChannel->responseStart() = mTransactionTimings.responseStart;
|
|
config.timedChannel->responseEnd() = mTransactionTimings.responseEnd;
|
|
}
|
|
|
|
if (aPreserveMethod) {
|
|
// since preserveMethod is true, we need to ensure that the appropriate
|
|
// request method gets set on the channel, regardless of whether or not
|
|
// we set the upload stream above. This means SetRequestMethod() will
|
|
// be called twice if ExplicitSetUploadStream() gets called above.
|
|
|
|
nsAutoCString method;
|
|
mRequestHead.Method(method);
|
|
config.method = Some(method);
|
|
|
|
if (mUploadStream) {
|
|
// rewind upload stream
|
|
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
|
|
if (seekable) {
|
|
seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
|
|
}
|
|
config.uploadStream = mUploadStream;
|
|
}
|
|
config.uploadStreamLength = mReqContentLength;
|
|
config.uploadStreamHasHeaders = mUploadStreamHasHeaders;
|
|
|
|
nsAutoCString contentType;
|
|
nsresult rv = mRequestHead.GetHeader(nsHttp::Content_Type, contentType);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
config.contentType = Some(contentType);
|
|
}
|
|
|
|
nsAutoCString contentLength;
|
|
rv = mRequestHead.GetHeader(nsHttp::Content_Length, contentLength);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
config.contentLength = Some(contentLength);
|
|
}
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
/* static */ void HttpBaseChannel::ConfigureReplacementChannel(
|
|
nsIChannel* newChannel, const ReplacementChannelConfig& config,
|
|
ReplacementReason aReason) {
|
|
nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(newChannel));
|
|
if (cos) {
|
|
cos->SetClassFlags(config.classOfService);
|
|
}
|
|
|
|
// Try to preserve the privacy bit if it has been overridden
|
|
if (config.privateBrowsing) {
|
|
nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel =
|
|
do_QueryInterface(newChannel);
|
|
if (newPBChannel) {
|
|
newPBChannel->SetPrivate(*config.privateBrowsing);
|
|
}
|
|
}
|
|
|
|
// Transfer the timing data (if we are dealing with an nsITimedChannel).
|
|
nsCOMPtr<nsITimedChannel> newTimedChannel(do_QueryInterface(newChannel));
|
|
if (config.timedChannel && newTimedChannel) {
|
|
newTimedChannel->SetTimingEnabled(config.timedChannel->timingEnabled());
|
|
|
|
// If we're an internal redirect, or a document channel replacement,
|
|
// then we shouldn't record any new timing for this and just copy
|
|
// over the existing values.
|
|
bool shouldHideTiming = aReason != ReplacementReason::Redirect;
|
|
if (shouldHideTiming) {
|
|
newTimedChannel->SetRedirectCount(config.timedChannel->redirectCount());
|
|
int8_t newCount = config.timedChannel->internalRedirectCount() + 1;
|
|
newTimedChannel->SetInternalRedirectCount(
|
|
std::max(newCount, config.timedChannel->internalRedirectCount()));
|
|
} else {
|
|
int8_t newCount = config.timedChannel->redirectCount() + 1;
|
|
newTimedChannel->SetRedirectCount(
|
|
std::max(newCount, config.timedChannel->redirectCount()));
|
|
newTimedChannel->SetInternalRedirectCount(
|
|
config.timedChannel->internalRedirectCount());
|
|
}
|
|
|
|
if (shouldHideTiming) {
|
|
if (!config.timedChannel->channelCreation().IsNull()) {
|
|
newTimedChannel->SetChannelCreation(
|
|
config.timedChannel->channelCreation());
|
|
}
|
|
|
|
if (!config.timedChannel->asyncOpen().IsNull()) {
|
|
newTimedChannel->SetAsyncOpen(config.timedChannel->asyncOpen());
|
|
}
|
|
}
|
|
|
|
// If the RedirectStart is null, we will use the AsyncOpen value of the
|
|
// previous channel (this is the first redirect in the redirects chain).
|
|
if (config.timedChannel->redirectStart().IsNull()) {
|
|
// Only do this for real redirects. Internal redirects should be hidden.
|
|
if (!shouldHideTiming) {
|
|
newTimedChannel->SetRedirectStart(config.timedChannel->asyncOpen());
|
|
}
|
|
} else {
|
|
newTimedChannel->SetRedirectStart(config.timedChannel->redirectStart());
|
|
}
|
|
|
|
// For internal redirects just propagate the last redirect end time
|
|
// forward. Otherwise the new redirect end time is the last response
|
|
// end time.
|
|
TimeStamp newRedirectEnd;
|
|
if (shouldHideTiming) {
|
|
newRedirectEnd = config.timedChannel->redirectEnd();
|
|
} else {
|
|
newRedirectEnd = config.timedChannel->responseEnd();
|
|
}
|
|
newTimedChannel->SetRedirectEnd(newRedirectEnd);
|
|
|
|
newTimedChannel->SetInitiatorType(config.timedChannel->initiatorType());
|
|
|
|
nsCOMPtr<nsILoadInfo> loadInfo = newChannel->LoadInfo();
|
|
MOZ_ASSERT(loadInfo);
|
|
|
|
newTimedChannel->SetAllRedirectsSameOrigin(
|
|
config.timedChannel->allRedirectsSameOrigin());
|
|
|
|
if (config.timedChannel->timingAllowCheckForPrincipal()) {
|
|
newTimedChannel->SetAllRedirectsPassTimingAllowCheck(
|
|
config.timedChannel->allRedirectsPassTimingAllowCheck() &&
|
|
*config.timedChannel->timingAllowCheckForPrincipal());
|
|
}
|
|
|
|
// Propagate service worker measurements across redirects. The
|
|
// PeformanceResourceTiming.workerStart API expects to see the
|
|
// worker start time after a redirect.
|
|
newTimedChannel->SetLaunchServiceWorkerStart(
|
|
config.timedChannel->launchServiceWorkerStart());
|
|
newTimedChannel->SetLaunchServiceWorkerEnd(
|
|
config.timedChannel->launchServiceWorkerEnd());
|
|
newTimedChannel->SetDispatchFetchEventStart(
|
|
config.timedChannel->dispatchFetchEventStart());
|
|
newTimedChannel->SetDispatchFetchEventEnd(
|
|
config.timedChannel->dispatchFetchEventEnd());
|
|
newTimedChannel->SetHandleFetchEventStart(
|
|
config.timedChannel->handleFetchEventStart());
|
|
newTimedChannel->SetHandleFetchEventEnd(
|
|
config.timedChannel->handleFetchEventEnd());
|
|
}
|
|
|
|
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
|
|
if (!httpChannel) {
|
|
return; // no other options to set
|
|
}
|
|
|
|
if (config.uploadStream) {
|
|
nsCOMPtr<nsIUploadChannel> uploadChannel = do_QueryInterface(httpChannel);
|
|
nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(httpChannel);
|
|
if (uploadChannel2 || uploadChannel) {
|
|
// replicate original call to SetUploadStream...
|
|
if (uploadChannel2) {
|
|
const nsACString& ctype =
|
|
config.contentType ? *config.contentType : VoidCString();
|
|
// If header is not present mRequestHead.HasHeaderValue will truncated
|
|
// it. But we want to end up with a void string, not an empty string,
|
|
// because ExplicitSetUploadStream treats the former as "no header" and
|
|
// the latter as "header with empty string value".
|
|
|
|
const nsACString& method =
|
|
config.method ? *config.method : VoidCString();
|
|
|
|
uploadChannel2->ExplicitSetUploadStream(
|
|
config.uploadStream, ctype, config.uploadStreamLength, method,
|
|
config.uploadStreamHasHeaders);
|
|
} else {
|
|
if (config.uploadStreamHasHeaders) {
|
|
uploadChannel->SetUploadStream(config.uploadStream, EmptyCString(),
|
|
config.uploadStreamLength);
|
|
} else {
|
|
nsAutoCString ctype;
|
|
if (config.contentType) {
|
|
ctype = *config.contentType;
|
|
} else {
|
|
ctype = "application/octet-stream"_ns;
|
|
}
|
|
if (config.contentLength && !config.contentLength->IsEmpty()) {
|
|
uploadChannel->SetUploadStream(
|
|
config.uploadStream, ctype,
|
|
nsCRT::atoll(config.contentLength->get()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (config.referrerInfo) {
|
|
DebugOnly<nsresult> success;
|
|
success = httpChannel->SetReferrerInfo(config.referrerInfo);
|
|
MOZ_ASSERT(NS_SUCCEEDED(success));
|
|
}
|
|
|
|
if (config.method) {
|
|
DebugOnly<nsresult> rv = httpChannel->SetRequestMethod(*config.method);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}
|
|
}
|
|
|
|
HttpBaseChannel::ReplacementChannelConfig::ReplacementChannelConfig(
|
|
const dom::ReplacementChannelConfigInit& aInit) {
|
|
redirectFlags = aInit.redirectFlags();
|
|
classOfService = aInit.classOfService();
|
|
privateBrowsing = aInit.privateBrowsing();
|
|
method = aInit.method();
|
|
referrerInfo = aInit.referrerInfo();
|
|
timedChannel = aInit.timedChannel();
|
|
if (RemoteLazyInputStreamChild* actor =
|
|
static_cast<RemoteLazyInputStreamChild*>(aInit.uploadStreamChild())) {
|
|
uploadStreamLength = actor->Size();
|
|
uploadStream = actor->CreateStream();
|
|
// actor can be deleted by CreateStream, so don't touch it
|
|
// after this.
|
|
} else {
|
|
uploadStreamLength = 0;
|
|
}
|
|
uploadStreamHasHeaders = aInit.uploadStreamHasHeaders();
|
|
contentType = aInit.contentType();
|
|
contentLength = aInit.contentLength();
|
|
}
|
|
|
|
dom::ReplacementChannelConfigInit
|
|
HttpBaseChannel::ReplacementChannelConfig::Serialize(
|
|
dom::ContentParent* aParent) {
|
|
dom::ReplacementChannelConfigInit config;
|
|
config.redirectFlags() = redirectFlags;
|
|
config.classOfService() = classOfService;
|
|
config.privateBrowsing() = privateBrowsing;
|
|
config.method() = method;
|
|
config.referrerInfo() = referrerInfo;
|
|
config.timedChannel() = timedChannel;
|
|
if (uploadStream) {
|
|
RemoteLazyStream ipdlStream;
|
|
RemoteLazyInputStreamUtils::SerializeInputStream(
|
|
uploadStream, uploadStreamLength, ipdlStream, aParent);
|
|
config.uploadStreamParent() = ipdlStream;
|
|
}
|
|
config.uploadStreamHasHeaders() = uploadStreamHasHeaders;
|
|
config.contentType() = contentType;
|
|
config.contentLength() = contentLength;
|
|
|
|
return config;
|
|
}
|
|
|
|
nsresult HttpBaseChannel::SetupReplacementChannel(nsIURI* newURI,
|
|
nsIChannel* newChannel,
|
|
bool preserveMethod,
|
|
uint32_t redirectFlags) {
|
|
nsresult rv;
|
|
|
|
LOG(
|
|
("HttpBaseChannel::SetupReplacementChannel "
|
|
"[this=%p newChannel=%p preserveMethod=%d]",
|
|
this, newChannel, preserveMethod));
|
|
|
|
// 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, this fulfills
|
|
// that request - newURI is the original URI of the channel.
|
|
nsCOMPtr<nsILoadInfo> newLoadInfo = newChannel->LoadInfo();
|
|
nsCOMPtr<nsIURI> resultPrincipalURI;
|
|
rv = newLoadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (!resultPrincipalURI) {
|
|
rv = newLoadInfo->SetResultPrincipalURI(newURI);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
nsLoadFlags loadFlags = mLoadFlags;
|
|
loadFlags |= LOAD_REPLACE;
|
|
|
|
// if the original channel was using SSL and this channel is not using
|
|
// SSL, then no need to inhibit persistent caching. however, if the
|
|
// original channel was not using SSL and has INHIBIT_PERSISTENT_CACHING
|
|
// set, then allow the flag to apply to the redirected channel as well.
|
|
// since we force set INHIBIT_PERSISTENT_CACHING on all HTTPS channels,
|
|
// we only need to check if the original channel was using SSL.
|
|
if (mURI->SchemeIs("https")) {
|
|
loadFlags &= ~INHIBIT_PERSISTENT_CACHING;
|
|
}
|
|
|
|
// Do not pass along LOAD_CHECK_OFFLINE_CACHE
|
|
loadFlags &= ~nsICachingChannel::LOAD_CHECK_OFFLINE_CACHE;
|
|
newChannel->SetLoadFlags(loadFlags);
|
|
|
|
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
|
|
|
|
ReplacementReason redirectType =
|
|
(redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL)
|
|
? ReplacementReason::InternalRedirect
|
|
: ReplacementReason::Redirect;
|
|
ReplacementChannelConfig config = CloneReplacementChannelConfig(
|
|
preserveMethod, redirectFlags, redirectType);
|
|
ConfigureReplacementChannel(newChannel, config, redirectType);
|
|
|
|
// Check whether or not this was a cross-domain redirect.
|
|
nsCOMPtr<nsITimedChannel> newTimedChannel(do_QueryInterface(newChannel));
|
|
if (config.timedChannel && newTimedChannel) {
|
|
newTimedChannel->SetAllRedirectsSameOrigin(
|
|
config.timedChannel->allRedirectsSameOrigin() &&
|
|
SameOriginWithOriginalUri(newURI));
|
|
}
|
|
|
|
newChannel->SetLoadGroup(mLoadGroup);
|
|
newChannel->SetNotificationCallbacks(mCallbacks);
|
|
|
|
if (!httpChannel) return NS_OK; // no other options to set
|
|
|
|
// Preserve the CORS preflight information.
|
|
nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(newChannel);
|
|
if (httpInternal) {
|
|
httpInternal->SetLastRedirectFlags(redirectFlags);
|
|
|
|
if (mRequireCORSPreflight) {
|
|
httpInternal->SetCorsPreflightParameters(mUnsafeHeaders, false);
|
|
}
|
|
}
|
|
|
|
// convey the mAllowSTS flags
|
|
rv = httpChannel->SetAllowSTS(mAllowSTS);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
// convey the Accept header value
|
|
{
|
|
nsAutoCString oldAcceptValue;
|
|
nsresult hasHeader = mRequestHead.GetHeader(nsHttp::Accept, oldAcceptValue);
|
|
if (NS_SUCCEEDED(hasHeader)) {
|
|
rv = httpChannel->SetRequestHeader("Accept"_ns, oldAcceptValue, false);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}
|
|
}
|
|
|
|
// convey the User-Agent header value
|
|
// since we might be setting custom user agent from DevTools.
|
|
if (httpInternal && mCorsMode == CORS_MODE_NO_CORS &&
|
|
redirectType == ReplacementReason::Redirect) {
|
|
nsAutoCString oldUserAgent;
|
|
nsresult hasHeader =
|
|
mRequestHead.GetHeader(nsHttp::User_Agent, oldUserAgent);
|
|
if (NS_SUCCEEDED(hasHeader)) {
|
|
rv = httpChannel->SetRequestHeader("User-Agent"_ns, oldUserAgent, false);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}
|
|
}
|
|
|
|
// share the request context - see bug 1236650
|
|
rv = httpChannel->SetRequestContextID(mRequestContextID);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
// When on the parent process, the channel can't attempt to get it itself.
|
|
// When on the child process, it would be waste to query it again.
|
|
rv = httpChannel->SetTopLevelOuterContentWindowId(
|
|
mTopLevelOuterContentWindowId);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
// Not setting this flag would break carrying permissions down to the child
|
|
// process when the channel is artificially forced to be a main document load.
|
|
rv = httpChannel->SetIsMainDocumentChannel(mForceMainDocumentChannel);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
// Preserve the loading order
|
|
nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(newChannel);
|
|
if (p) {
|
|
p->SetPriority(mPriority);
|
|
}
|
|
|
|
if (httpInternal) {
|
|
// Convey third party cookie, conservative, and spdy flags.
|
|
rv = httpInternal->SetThirdPartyFlags(mThirdPartyFlags);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
rv = httpInternal->SetAllowSpdy(mAllowSpdy);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
rv = httpInternal->SetAllowAltSvc(mAllowAltSvc);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
rv = httpInternal->SetBeConservative(mBeConservative);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
rv = httpInternal->SetIsTRRServiceChannel(mIsTRRServiceChannel);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
rv = httpInternal->SetTlsFlags(mTlsFlags);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
// Ensure the type of realChannel involves all types it may redirect to.
|
|
// Such as nsHttpChannel and InterceptedChannel.
|
|
// Even thought InterceptedChannel itself doesn't require these information,
|
|
// it may still be necessary for the following redirections.
|
|
// E.g. nsHttpChannel -> InterceptedChannel -> nsHttpChannel
|
|
RefPtr<HttpBaseChannel> realChannel;
|
|
CallQueryInterface(newChannel, realChannel.StartAssignment());
|
|
if (realChannel) {
|
|
realChannel->SetTopWindowURI(mTopWindowURI);
|
|
}
|
|
|
|
// update the DocumentURI indicator since we are being redirected.
|
|
// if this was a top-level document channel, then the new channel
|
|
// should have its mDocumentURI point to newURI; otherwise, we
|
|
// just need to pass along our mDocumentURI to the new channel.
|
|
if (newURI && (mURI == mDocumentURI))
|
|
rv = httpInternal->SetDocumentURI(newURI);
|
|
else
|
|
rv = httpInternal->SetDocumentURI(mDocumentURI);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
// if there is a chain of keys for redirect-responses we transfer it to
|
|
// the new channel (see bug #561276)
|
|
if (mRedirectedCachekeys) {
|
|
LOG(
|
|
("HttpBaseChannel::SetupReplacementChannel "
|
|
"[this=%p] transferring chain of redirect cache-keys",
|
|
this));
|
|
rv = httpInternal->SetCacheKeysRedirectChain(
|
|
mRedirectedCachekeys.release());
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}
|
|
|
|
// Preserve CORS mode flag.
|
|
rv = httpInternal->SetCorsMode(mCorsMode);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
// Preserve Redirect mode flag.
|
|
rv = httpInternal->SetRedirectMode(mRedirectMode);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
// Preserve Integrity metadata.
|
|
rv = httpInternal->SetIntegrityMetadata(mIntegrityMetadata);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
httpInternal->SetAltDataForChild(mAltDataForChild);
|
|
if (mDisableAltDataCache) {
|
|
httpInternal->DisableAltDataCache();
|
|
}
|
|
}
|
|
|
|
// transfer application cache information
|
|
nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
|
|
do_QueryInterface(newChannel);
|
|
if (appCacheChannel) {
|
|
appCacheChannel->SetApplicationCache(mApplicationCache);
|
|
appCacheChannel->SetInheritApplicationCache(mInheritApplicationCache);
|
|
// We purposely avoid transfering mChooseApplicationCache.
|
|
}
|
|
|
|
// transfer any properties
|
|
nsCOMPtr<nsIWritablePropertyBag> bag(do_QueryInterface(newChannel));
|
|
if (bag) {
|
|
for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) {
|
|
bag->SetProperty(iter.Key(), iter.UserData());
|
|
}
|
|
}
|
|
|
|
// Pass the preferred alt-data type on to the new channel.
|
|
nsCOMPtr<nsICacheInfoChannel> cacheInfoChan(do_QueryInterface(newChannel));
|
|
if (cacheInfoChan) {
|
|
for (auto& data : mPreferredCachedAltDataTypes) {
|
|
cacheInfoChan->PreferAlternativeDataType(data.type(), data.contentType(),
|
|
data.deliverAltData());
|
|
}
|
|
}
|
|
|
|
if (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
|
|
nsIChannelEventSink::REDIRECT_STS_UPGRADE)) {
|
|
// Copy non-origin related headers to the new channel.
|
|
nsCOMPtr<nsIHttpHeaderVisitor> visitor =
|
|
new AddHeadersToChannelVisitor(httpChannel);
|
|
rv = mRequestHead.VisitHeaders(visitor);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}
|
|
|
|
// This channel has been redirected. Don't report timing info.
|
|
mTimingEnabled = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
// Redirect Tracking
|
|
bool HttpBaseChannel::SameOriginWithOriginalUri(nsIURI* aURI) {
|
|
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
|
bool isPrivateWin = mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
|
|
nsresult rv =
|
|
ssm->CheckSameOriginURI(aURI, mOriginalURI, false, isPrivateWin);
|
|
return (NS_SUCCEEDED(rv));
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIClassifiedChannel
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetMatchedList(nsACString& aList) {
|
|
aList = mMatchedList;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetMatchedProvider(nsACString& aProvider) {
|
|
aProvider = mMatchedProvider;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetMatchedFullHash(nsACString& aFullHash) {
|
|
aFullHash = mMatchedFullHash;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetMatchedInfo(const nsACString& aList,
|
|
const nsACString& aProvider,
|
|
const nsACString& aFullHash) {
|
|
NS_ENSURE_ARG(!aList.IsEmpty());
|
|
|
|
mMatchedList = aList;
|
|
mMatchedProvider = aProvider;
|
|
mMatchedFullHash = aFullHash;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetMatchedTrackingLists(nsTArray<nsCString>& aLists) {
|
|
aLists = mMatchedTrackingLists.Clone();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetMatchedTrackingFullHashes(
|
|
nsTArray<nsCString>& aFullHashes) {
|
|
aFullHashes = mMatchedTrackingFullHashes.Clone();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetMatchedTrackingInfo(
|
|
const nsTArray<nsCString>& aLists, const nsTArray<nsCString>& aFullHashes) {
|
|
NS_ENSURE_ARG(!aLists.IsEmpty());
|
|
// aFullHashes can be empty for non hash-matching algorithm, for example,
|
|
// host based test entries in preference.
|
|
|
|
mMatchedTrackingLists = aLists.Clone();
|
|
mMatchedTrackingFullHashes = aFullHashes.Clone();
|
|
return NS_OK;
|
|
}
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsITimedChannel
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetTimingEnabled(bool enabled) {
|
|
mTimingEnabled = enabled;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetTimingEnabled(bool* _retval) {
|
|
*_retval = mTimingEnabled;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetChannelCreation(TimeStamp* _retval) {
|
|
*_retval = mChannelCreationTimestamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetChannelCreation(TimeStamp aValue) {
|
|
MOZ_DIAGNOSTIC_ASSERT(!aValue.IsNull());
|
|
TimeDuration adjust = aValue - mChannelCreationTimestamp;
|
|
mChannelCreationTimestamp = aValue;
|
|
mChannelCreationTime += (PRTime)adjust.ToMicroseconds();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetAsyncOpen(TimeStamp* _retval) {
|
|
*_retval = mAsyncOpenTime;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetAsyncOpen(TimeStamp aValue) {
|
|
MOZ_DIAGNOSTIC_ASSERT(!aValue.IsNull());
|
|
mAsyncOpenTime = aValue;
|
|
mAsyncOpenTimeOverriden = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* @return the number of redirects. There is no check for cross-domain
|
|
* redirects. This check must be done by the consumers.
|
|
*/
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRedirectCount(uint8_t* aRedirectCount) {
|
|
*aRedirectCount = mRedirectCount;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetRedirectCount(uint8_t aRedirectCount) {
|
|
mRedirectCount = aRedirectCount;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetInternalRedirectCount(uint8_t* aRedirectCount) {
|
|
*aRedirectCount = mInternalRedirectCount;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetInternalRedirectCount(uint8_t aRedirectCount) {
|
|
mInternalRedirectCount = aRedirectCount;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRedirectStart(TimeStamp* _retval) {
|
|
*_retval = mRedirectStartTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetRedirectStart(TimeStamp aRedirectStart) {
|
|
mRedirectStartTimeStamp = aRedirectStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRedirectEnd(TimeStamp* _retval) {
|
|
*_retval = mRedirectEndTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetRedirectEnd(TimeStamp aRedirectEnd) {
|
|
mRedirectEndTimeStamp = aRedirectEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetAllRedirectsSameOrigin(bool* aAllRedirectsSameOrigin) {
|
|
*aAllRedirectsSameOrigin = mAllRedirectsSameOrigin;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetAllRedirectsSameOrigin(bool aAllRedirectsSameOrigin) {
|
|
mAllRedirectsSameOrigin = aAllRedirectsSameOrigin;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetAllRedirectsPassTimingAllowCheck(bool* aPassesCheck) {
|
|
*aPassesCheck = mAllRedirectsPassTimingAllowCheck;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetAllRedirectsPassTimingAllowCheck(bool aPassesCheck) {
|
|
mAllRedirectsPassTimingAllowCheck = aPassesCheck;
|
|
return NS_OK;
|
|
}
|
|
|
|
// http://www.w3.org/TR/resource-timing/#timing-allow-check
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::TimingAllowCheck(nsIPrincipal* aOrigin, bool* _retval) {
|
|
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
|
nsCOMPtr<nsIPrincipal> resourcePrincipal;
|
|
nsresult rv =
|
|
ssm->GetChannelURIPrincipal(this, getter_AddRefs(resourcePrincipal));
|
|
if (NS_FAILED(rv) || !resourcePrincipal || !aOrigin) {
|
|
*_retval = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
bool sameOrigin = false;
|
|
rv = resourcePrincipal->Equals(aOrigin, &sameOrigin);
|
|
if (NS_SUCCEEDED(rv) && sameOrigin) {
|
|
*_retval = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsAutoCString headerValue;
|
|
rv = GetResponseHeader("Timing-Allow-Origin"_ns, headerValue);
|
|
if (NS_FAILED(rv)) {
|
|
*_retval = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsAutoCString origin;
|
|
aOrigin->GetAsciiOrigin(origin);
|
|
|
|
Tokenizer p(headerValue);
|
|
Tokenizer::Token t;
|
|
|
|
p.Record();
|
|
nsAutoCString headerItem;
|
|
while (p.Next(t)) {
|
|
if (t.Type() == Tokenizer::TOKEN_EOF ||
|
|
t.Equals(Tokenizer::Token::Char(','))) {
|
|
p.Claim(headerItem);
|
|
nsHttp::TrimHTTPWhitespace(headerItem, headerItem);
|
|
// If the list item contains a case-sensitive match for the value of the
|
|
// origin, or a wildcard, return pass
|
|
if (headerItem == origin || headerItem == "*") {
|
|
*_retval = true;
|
|
return NS_OK;
|
|
}
|
|
// We start recording again for the following items in the list
|
|
p.Record();
|
|
}
|
|
}
|
|
|
|
*_retval = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLaunchServiceWorkerStart(TimeStamp* _retval) {
|
|
MOZ_ASSERT(_retval);
|
|
*_retval = mLaunchServiceWorkerStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) {
|
|
mLaunchServiceWorkerStart = aTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLaunchServiceWorkerEnd(TimeStamp* _retval) {
|
|
MOZ_ASSERT(_retval);
|
|
*_retval = mLaunchServiceWorkerEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetLaunchServiceWorkerEnd(TimeStamp aTimeStamp) {
|
|
mLaunchServiceWorkerEnd = aTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetDispatchFetchEventStart(TimeStamp* _retval) {
|
|
MOZ_ASSERT(_retval);
|
|
*_retval = mDispatchFetchEventStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetDispatchFetchEventStart(TimeStamp aTimeStamp) {
|
|
mDispatchFetchEventStart = aTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetDispatchFetchEventEnd(TimeStamp* _retval) {
|
|
MOZ_ASSERT(_retval);
|
|
*_retval = mDispatchFetchEventEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetDispatchFetchEventEnd(TimeStamp aTimeStamp) {
|
|
mDispatchFetchEventEnd = aTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetHandleFetchEventStart(TimeStamp* _retval) {
|
|
MOZ_ASSERT(_retval);
|
|
*_retval = mHandleFetchEventStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetHandleFetchEventStart(TimeStamp aTimeStamp) {
|
|
mHandleFetchEventStart = aTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetHandleFetchEventEnd(TimeStamp* _retval) {
|
|
MOZ_ASSERT(_retval);
|
|
*_retval = mHandleFetchEventEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetHandleFetchEventEnd(TimeStamp aTimeStamp) {
|
|
mHandleFetchEventEnd = aTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetDomainLookupStart(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.domainLookupStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetDomainLookupEnd(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.domainLookupEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetConnectStart(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.connectStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetTcpConnectEnd(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.tcpConnectEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetSecureConnectionStart(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.secureConnectionStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetConnectEnd(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.connectEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRequestStart(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.requestStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetResponseStart(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.responseStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetResponseEnd(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.responseEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetCacheReadStart(TimeStamp* _retval) {
|
|
*_retval = mCacheReadStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetCacheReadEnd(TimeStamp* _retval) {
|
|
*_retval = mCacheReadEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetInitiatorType(nsAString& aInitiatorType) {
|
|
aInitiatorType = mInitiatorType;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetInitiatorType(const nsAString& aInitiatorType) {
|
|
mInitiatorType = aInitiatorType;
|
|
return NS_OK;
|
|
}
|
|
|
|
#define IMPL_TIMING_ATTR(name) \
|
|
NS_IMETHODIMP \
|
|
HttpBaseChannel::Get##name##Time(PRTime* _retval) { \
|
|
TimeStamp stamp; \
|
|
Get##name(&stamp); \
|
|
if (stamp.IsNull()) { \
|
|
*_retval = 0; \
|
|
return NS_OK; \
|
|
} \
|
|
*_retval = \
|
|
mChannelCreationTime + \
|
|
(PRTime)((stamp - mChannelCreationTimestamp).ToSeconds() * 1e6); \
|
|
return NS_OK; \
|
|
}
|
|
|
|
IMPL_TIMING_ATTR(ChannelCreation)
|
|
IMPL_TIMING_ATTR(AsyncOpen)
|
|
IMPL_TIMING_ATTR(LaunchServiceWorkerStart)
|
|
IMPL_TIMING_ATTR(LaunchServiceWorkerEnd)
|
|
IMPL_TIMING_ATTR(DispatchFetchEventStart)
|
|
IMPL_TIMING_ATTR(DispatchFetchEventEnd)
|
|
IMPL_TIMING_ATTR(HandleFetchEventStart)
|
|
IMPL_TIMING_ATTR(HandleFetchEventEnd)
|
|
IMPL_TIMING_ATTR(DomainLookupStart)
|
|
IMPL_TIMING_ATTR(DomainLookupEnd)
|
|
IMPL_TIMING_ATTR(ConnectStart)
|
|
IMPL_TIMING_ATTR(TcpConnectEnd)
|
|
IMPL_TIMING_ATTR(SecureConnectionStart)
|
|
IMPL_TIMING_ATTR(ConnectEnd)
|
|
IMPL_TIMING_ATTR(RequestStart)
|
|
IMPL_TIMING_ATTR(ResponseStart)
|
|
IMPL_TIMING_ATTR(ResponseEnd)
|
|
IMPL_TIMING_ATTR(CacheReadStart)
|
|
IMPL_TIMING_ATTR(CacheReadEnd)
|
|
IMPL_TIMING_ATTR(RedirectStart)
|
|
IMPL_TIMING_ATTR(RedirectEnd)
|
|
|
|
#undef IMPL_TIMING_ATTR
|
|
|
|
mozilla::dom::PerformanceStorage* HttpBaseChannel::GetPerformanceStorage() {
|
|
// If performance timing is disabled, there is no need for the Performance
|
|
// object anymore.
|
|
if (!mTimingEnabled) {
|
|
return nullptr;
|
|
}
|
|
|
|
// There is no point in continuing, since the performance object in the parent
|
|
// isn't the same as the one in the child which will be reporting resource
|
|
// performance.
|
|
if (XRE_IsE10sParentProcess()) {
|
|
return nullptr;
|
|
}
|
|
return mLoadInfo->GetPerformanceStorage();
|
|
}
|
|
|
|
void HttpBaseChannel::MaybeReportTimingData() {
|
|
mozilla::dom::PerformanceStorage* documentPerformance =
|
|
GetPerformanceStorage();
|
|
if (documentPerformance) {
|
|
documentPerformance->AddEntry(this, this);
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetReportResourceTiming(bool enabled) {
|
|
mReportTiming = enabled;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetReportResourceTiming(bool* _retval) {
|
|
*_retval = mReportTiming;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsIURI* HttpBaseChannel::GetReferringPage() {
|
|
nsCOMPtr<nsPIDOMWindowInner> pDomWindow = GetInnerDOMWindow();
|
|
if (!pDomWindow) {
|
|
return nullptr;
|
|
}
|
|
return pDomWindow->GetDocumentURI();
|
|
}
|
|
|
|
nsPIDOMWindowInner* HttpBaseChannel::GetInnerDOMWindow() {
|
|
nsCOMPtr<nsILoadContext> loadContext;
|
|
NS_QueryNotificationCallbacks(this, loadContext);
|
|
if (!loadContext) {
|
|
return nullptr;
|
|
}
|
|
nsCOMPtr<mozIDOMWindowProxy> domWindow;
|
|
loadContext->GetAssociatedWindow(getter_AddRefs(domWindow));
|
|
if (!domWindow) {
|
|
return nullptr;
|
|
}
|
|
auto* pDomWindow = nsPIDOMWindowOuter::From(domWindow);
|
|
if (!pDomWindow) {
|
|
return nullptr;
|
|
}
|
|
nsCOMPtr<nsPIDOMWindowInner> innerWindow =
|
|
pDomWindow->GetCurrentInnerWindow();
|
|
if (!innerWindow) {
|
|
return nullptr;
|
|
}
|
|
|
|
return innerWindow;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIThrottledInputChannel
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetThrottleQueue(nsIInputChannelThrottleQueue* aQueue) {
|
|
if (!XRE_IsParentProcess()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mThrottleQueue = aQueue;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetThrottleQueue(nsIInputChannelThrottleQueue** aQueue) {
|
|
NS_ENSURE_ARG_POINTER(aQueue);
|
|
nsCOMPtr<nsIInputChannelThrottleQueue> queue = mThrottleQueue;
|
|
queue.forget(aQueue);
|
|
return NS_OK;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
bool HttpBaseChannel::EnsureRequestContextID() {
|
|
if (mRequestContextID) {
|
|
// Already have a request context ID, no need to do the rest of this work
|
|
LOG(("HttpBaseChannel::EnsureRequestContextID this=%p id=%" PRIx64, this,
|
|
mRequestContextID));
|
|
return true;
|
|
}
|
|
|
|
// Find the loadgroup at the end of the chain in order
|
|
// to make sure all channels derived from the load group
|
|
// use the same connection scope.
|
|
nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(mLoadGroup);
|
|
if (!childLoadGroup) {
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsILoadGroup> rootLoadGroup;
|
|
childLoadGroup->GetRootLoadGroup(getter_AddRefs(rootLoadGroup));
|
|
if (!rootLoadGroup) {
|
|
return false;
|
|
}
|
|
|
|
// Set the load group connection scope on this channel and its transaction
|
|
rootLoadGroup->GetRequestContextID(&mRequestContextID);
|
|
|
|
LOG(("HttpBaseChannel::EnsureRequestContextID this=%p id=%" PRIx64, this,
|
|
mRequestContextID));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HttpBaseChannel::EnsureRequestContext() {
|
|
if (mRequestContext) {
|
|
// Already have a request context, no need to do the rest of this work
|
|
return true;
|
|
}
|
|
|
|
if (!EnsureRequestContextID()) {
|
|
return false;
|
|
}
|
|
|
|
nsIRequestContextService* rcsvc = gHttpHandler->GetRequestContextService();
|
|
if (!rcsvc) {
|
|
return false;
|
|
}
|
|
|
|
rcsvc->GetRequestContext(mRequestContextID, getter_AddRefs(mRequestContext));
|
|
if (!mRequestContext) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void HttpBaseChannel::EnsureTopLevelOuterContentWindowId() {
|
|
if (mTopLevelOuterContentWindowId) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsILoadContext> loadContext;
|
|
GetCallback(loadContext);
|
|
if (!loadContext) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<mozIDOMWindowProxy> topWindow;
|
|
loadContext->GetTopWindow(getter_AddRefs(topWindow));
|
|
if (!topWindow) {
|
|
return;
|
|
}
|
|
|
|
mTopLevelOuterContentWindowId =
|
|
nsPIDOMWindowOuter::From(topWindow)->WindowID();
|
|
}
|
|
|
|
void HttpBaseChannel::SetCorsPreflightParameters(
|
|
const nsTArray<nsCString>& aUnsafeHeaders,
|
|
bool aShouldStripRequestBodyHeader) {
|
|
MOZ_RELEASE_ASSERT(!mRequestObserversCalled);
|
|
|
|
mRequireCORSPreflight = true;
|
|
mUnsafeHeaders = aUnsafeHeaders.Clone();
|
|
if (aShouldStripRequestBodyHeader) {
|
|
mUnsafeHeaders.RemoveElementsBy([&](const nsCString& aHeader) {
|
|
return aHeader.LowerCaseEqualsASCII("content-type") ||
|
|
aHeader.LowerCaseEqualsASCII("content-encoding") ||
|
|
aHeader.LowerCaseEqualsASCII("content-language") ||
|
|
aHeader.LowerCaseEqualsASCII("content-location");
|
|
});
|
|
}
|
|
}
|
|
|
|
void HttpBaseChannel::SetAltDataForChild(bool aIsForChild) {
|
|
mAltDataForChild = aIsForChild;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetBlockAuthPrompt(bool* aValue) {
|
|
if (!aValue) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
*aValue = mBlockAuthPrompt;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetBlockAuthPrompt(bool aValue) {
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
|
|
mBlockAuthPrompt = aValue;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetConnectionInfoHashKey(nsACString& aConnectionInfoHashKey) {
|
|
if (!mConnectionInfo) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
aConnectionInfoHashKey.Assign(mConnectionInfo->HashKey());
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLastRedirectFlags(uint32_t* aValue) {
|
|
NS_ENSURE_ARG(aValue);
|
|
*aValue = mLastRedirectFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetLastRedirectFlags(uint32_t aValue) {
|
|
mLastRedirectFlags = aValue;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetNavigationStartTimeStamp(TimeStamp* aTimeStamp) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetNavigationStartTimeStamp(TimeStamp aTimeStamp) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
nsresult HttpBaseChannel::CheckRedirectLimit(uint32_t aRedirectFlags) const {
|
|
if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
|
|
// Some platform features, like Service Workers, depend on internal
|
|
// redirects. We should allow some number of internal redirects above
|
|
// and beyond the normal redirect limit so these features continue
|
|
// to work.
|
|
static const int8_t kMinInternalRedirects = 5;
|
|
|
|
if (mInternalRedirectCount >= (mRedirectionLimit + kMinInternalRedirects)) {
|
|
LOG(("internal redirection limit reached!\n"));
|
|
return NS_ERROR_REDIRECT_LOOP;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(aRedirectFlags & (nsIChannelEventSink::REDIRECT_TEMPORARY |
|
|
nsIChannelEventSink::REDIRECT_PERMANENT |
|
|
nsIChannelEventSink::REDIRECT_STS_UPGRADE));
|
|
|
|
if (mRedirectCount >= mRedirectionLimit) {
|
|
LOG(("redirection limit reached!\n"));
|
|
return NS_ERROR_REDIRECT_LOOP;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// NOTE: This function duplicates code from nsBaseChannel. This will go away
|
|
// once HTTP uses nsBaseChannel (part of bug 312760)
|
|
/* static */
|
|
void HttpBaseChannel::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);
|
|
}
|
|
}
|
|
|
|
template <class T>
|
|
static void ParseServerTimingHeader(
|
|
const UniquePtr<T>& aHeader, nsTArray<nsCOMPtr<nsIServerTiming>>& aOutput) {
|
|
if (!aHeader) {
|
|
return;
|
|
}
|
|
|
|
nsAutoCString serverTimingHeader;
|
|
Unused << aHeader->GetHeader(nsHttp::Server_Timing, serverTimingHeader);
|
|
if (serverTimingHeader.IsEmpty()) {
|
|
return;
|
|
}
|
|
|
|
ServerTimingParser parser(serverTimingHeader);
|
|
parser.Parse();
|
|
|
|
nsTArray<nsCOMPtr<nsIServerTiming>> array = parser.TakeServerTimingHeaders();
|
|
aOutput.AppendElements(array);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetServerTiming(nsIArray** aServerTiming) {
|
|
nsresult rv;
|
|
NS_ENSURE_ARG_POINTER(aServerTiming);
|
|
|
|
nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsTArray<nsCOMPtr<nsIServerTiming>> data;
|
|
rv = GetNativeServerTiming(data);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
for (const auto& entry : data) {
|
|
array->AppendElement(entry);
|
|
}
|
|
|
|
array.forget(aServerTiming);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetNativeServerTiming(
|
|
nsTArray<nsCOMPtr<nsIServerTiming>>& aServerTiming) {
|
|
aServerTiming.Clear();
|
|
|
|
if (mURI->SchemeIs("https")) {
|
|
ParseServerTimingHeader(mResponseHead, aServerTiming);
|
|
ParseServerTimingHeader(mResponseTrailers, aServerTiming);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::CancelByURLClassifier(nsresult aErrorCode) {
|
|
MOZ_ASSERT(
|
|
UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
|
|
return Cancel(aErrorCode);
|
|
}
|
|
|
|
void HttpBaseChannel::SetIPv4Disabled() { mCaps |= NS_HTTP_DISABLE_IPV4; }
|
|
|
|
void HttpBaseChannel::SetIPv6Disabled() { mCaps |= NS_HTTP_DISABLE_IPV6; }
|
|
|
|
NS_IMETHODIMP HttpBaseChannel::GetResponseEmbedderPolicy(
|
|
nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) {
|
|
*aOutPolicy = nsILoadInfo::EMBEDDER_POLICY_NULL;
|
|
if (!mResponseHead) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
if (!nsContentUtils::ComputeIsSecureContext(this)) {
|
|
// Feature is only available for secure contexts.
|
|
return NS_OK;
|
|
}
|
|
|
|
nsAutoCString content;
|
|
Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Embedder_Policy,
|
|
content);
|
|
|
|
*aOutPolicy = NS_GetCrossOriginEmbedderPolicyFromHeader(content);
|
|
return NS_OK;
|
|
}
|
|
|
|
// Obtain a cross-origin opener-policy from a response response and a
|
|
// cross-origin opener policy initiator.
|
|
// https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e
|
|
NS_IMETHODIMP HttpBaseChannel::ComputeCrossOriginOpenerPolicy(
|
|
nsILoadInfo::CrossOriginOpenerPolicy aInitiatorPolicy,
|
|
nsILoadInfo::CrossOriginOpenerPolicy* aOutPolicy) {
|
|
MOZ_ASSERT(aOutPolicy);
|
|
*aOutPolicy = nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
|
|
|
|
if (!mResponseHead) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
// COOP headers are ignored for insecure-context loads.
|
|
if (!nsContentUtils::ComputeIsSecureContext(this)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsAutoCString openerPolicy;
|
|
Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Opener_Policy,
|
|
openerPolicy);
|
|
|
|
// Cross-Origin-Opener-Policy = %s"same-origin" /
|
|
// %s"same-origin-allow-popups" /
|
|
// %s"unsafe-none"; case-sensitive
|
|
|
|
nsILoadInfo::CrossOriginOpenerPolicy policy =
|
|
nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
|
|
|
|
if (openerPolicy.EqualsLiteral("same-origin")) {
|
|
policy = nsILoadInfo::OPENER_POLICY_SAME_ORIGIN;
|
|
} else if (openerPolicy.EqualsLiteral("same-origin-allow-popups")) {
|
|
policy = nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS;
|
|
}
|
|
|
|
if (policy == nsILoadInfo::OPENER_POLICY_SAME_ORIGIN) {
|
|
nsILoadInfo::CrossOriginEmbedderPolicy coep =
|
|
nsILoadInfo::EMBEDDER_POLICY_NULL;
|
|
if (NS_SUCCEEDED(GetResponseEmbedderPolicy(&coep)) &&
|
|
coep == nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP) {
|
|
policy =
|
|
nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
|
|
}
|
|
}
|
|
|
|
*aOutPolicy = policy;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetCrossOriginOpenerPolicy(
|
|
nsILoadInfo::CrossOriginOpenerPolicy* aPolicy) {
|
|
MOZ_ASSERT(aPolicy);
|
|
if (!aPolicy) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
// If this method is called before OnStartRequest (ie. before we call
|
|
// ComputeCrossOriginOpenerPolicy) or if we were unable to compute the
|
|
// policy we'll throw an error.
|
|
if (!mOnStartRequestCalled) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
*aPolicy = mComputedCrossOriginOpenerPolicy;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::HasCrossOriginOpenerPolicyMismatch(bool* aIsMismatch) {
|
|
// This should only be called in parent process.
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
*aIsMismatch = mHasCrossOriginOpenerPolicyMismatch;
|
|
return NS_OK;
|
|
}
|
|
|
|
void HttpBaseChannel::MaybeFlushConsoleReports() {
|
|
// If this channel is part of a loadGroup, we can flush the console reports
|
|
// immediately.
|
|
nsCOMPtr<nsILoadGroup> loadGroup;
|
|
nsresult rv = GetLoadGroup(getter_AddRefs(loadGroup));
|
|
if (NS_SUCCEEDED(rv) && loadGroup) {
|
|
FlushConsoleReports(loadGroup);
|
|
}
|
|
}
|
|
|
|
void HttpBaseChannel::DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() {}
|
|
|
|
} // namespace net
|
|
} // namespace mozilla
|