6841 lines
219 KiB
C++
6841 lines
219 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 "ReferrerInfo.h"
|
||
#include "mozIRemoteLazyInputStream.h"
|
||
#include "mozIThirdPartyUtil.h"
|
||
#include "mozilla/LoadInfo.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/Mutex.h"
|
||
#include "mozilla/NullPrincipal.h"
|
||
#include "mozilla/PermissionManager.h"
|
||
#include "mozilla/Components.h"
|
||
#include "mozilla/StaticPrefs_browser.h"
|
||
#include "mozilla/StaticPrefs_fission.h"
|
||
#include "mozilla/StaticPrefs_network.h"
|
||
#include "mozilla/StaticPrefs_security.h"
|
||
#include "mozilla/glean/NetwerkProtocolHttpMetrics.h"
|
||
#include "mozilla/Telemetry.h"
|
||
#include "mozilla/Tokenizer.h"
|
||
#include "mozilla/browser/NimbusFeatures.h"
|
||
#include "mozilla/dom/BrowsingContext.h"
|
||
#include "mozilla/dom/CanonicalBrowsingContext.h"
|
||
#include "mozilla/dom/Document.h"
|
||
#include "mozilla/dom/FetchPriority.h"
|
||
#include "mozilla/dom/nsHTTPSOnlyUtils.h"
|
||
#include "mozilla/dom/nsMixedContentBlocker.h"
|
||
#include "mozilla/dom/Performance.h"
|
||
#include "mozilla/dom/PerformanceStorage.h"
|
||
#include "mozilla/dom/ProcessIsolation.h"
|
||
#include "mozilla/dom/RequestBinding.h"
|
||
#include "mozilla/dom/WindowGlobalParent.h"
|
||
#include "mozilla/net/OpaqueResponseUtils.h"
|
||
#include "mozilla/net/UrlClassifierCommon.h"
|
||
#include "mozilla/net/UrlClassifierFeatureFactory.h"
|
||
#include "nsBufferedStreams.h"
|
||
#include "nsCOMPtr.h"
|
||
#include "nsCRT.h"
|
||
#include "nsContentSecurityManager.h"
|
||
#include "nsContentSecurityUtils.h"
|
||
#include "nsContentUtils.h"
|
||
#include "nsDebug.h"
|
||
#include "nsEscape.h"
|
||
#include "nsGlobalWindowInner.h"
|
||
#include "nsGlobalWindowOuter.h"
|
||
#include "nsHttpChannel.h"
|
||
#include "nsHTTPCompressConv.h"
|
||
#include "nsHttpHandler.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 "nsIDNSService.h"
|
||
#include "nsIEncodedChannel.h"
|
||
#include "nsIHttpHeaderVisitor.h"
|
||
#include "nsILoadGroupChild.h"
|
||
#include "nsIMIMEInputStream.h"
|
||
#include "nsIMultiplexInputStream.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 "nsString.h"
|
||
#include "nsThreadUtils.h"
|
||
#include "nsURLHelper.h"
|
||
#include "mozilla/RemoteLazyInputStreamChild.h"
|
||
#include "mozilla/net/SFVService.h"
|
||
#include "mozilla/dom/ContentChild.h"
|
||
#include "nsQueryObject.h"
|
||
|
||
using mozilla::dom::RequestMode;
|
||
|
||
#define LOGORB(msg, ...) \
|
||
MOZ_LOG(GetORBLog(), LogLevel::Debug, \
|
||
("%s: %p " msg, __func__, this, ##__VA_ARGS__))
|
||
|
||
namespace mozilla {
|
||
namespace net {
|
||
|
||
static bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader) {
|
||
// IMPORTANT: keep this list ASCII-code sorted
|
||
static nsHttpAtomLiteral const* blackList[] = {
|
||
&nsHttp::Accept,
|
||
&nsHttp::Accept_Encoding,
|
||
&nsHttp::Accept_Language,
|
||
&nsHttp::Alternate_Service_Used,
|
||
&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.get(), aVal->get());
|
||
}
|
||
int operator()(nsHttpAtomLiteral const* aVal) const {
|
||
if (mTarget == *aVal) {
|
||
return 0;
|
||
}
|
||
return strcmp(mTarget.get(), aVal->get());
|
||
}
|
||
};
|
||
|
||
size_t unused;
|
||
return BinarySearchIf(blackList, 0, std::size(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)
|
||
|
||
static OpaqueResponseFilterFetch ConfiguredFilterFetchResponseBehaviour() {
|
||
uint32_t pref = StaticPrefs::
|
||
browser_opaqueResponseBlocking_filterFetchResponse_DoNotUseDirectly();
|
||
if (NS_WARN_IF(pref >
|
||
static_cast<uint32_t>(OpaqueResponseFilterFetch::All))) {
|
||
return OpaqueResponseFilterFetch::All;
|
||
}
|
||
|
||
return static_cast<OpaqueResponseFilterFetch>(pref);
|
||
}
|
||
|
||
HttpBaseChannel::HttpBaseChannel()
|
||
: mReportCollector(new ConsoleReportCollector()),
|
||
mHttpHandler(gHttpHandler),
|
||
mChannelCreationTime(0),
|
||
mComputedCrossOriginOpenerPolicy(nsILoadInfo::OPENER_POLICY_UNSAFE_NONE),
|
||
mStartPos(UINT64_MAX),
|
||
mTransferSize(0),
|
||
mRequestSize(0),
|
||
mDecodedBodySize(0),
|
||
mSupportsHTTP3(false),
|
||
mEncodedBodySize(0),
|
||
mRequestContextID(0),
|
||
mContentWindowId(0),
|
||
mBrowserId(0),
|
||
mAltDataLength(-1),
|
||
mChannelId(0),
|
||
mReqContentLength(0U),
|
||
mStatus(NS_OK),
|
||
mCanceled(false),
|
||
mFirstPartyClassificationFlags(0),
|
||
mThirdPartyClassificationFlags(0),
|
||
mLoadFlags(LOAD_NORMAL),
|
||
mCaps(0),
|
||
mClassOfService(0, false),
|
||
mTlsFlags(0),
|
||
mSuspendCount(0),
|
||
mInitialRwin(0),
|
||
mProxyResolveFlags(0),
|
||
mContentDispositionHint(UINT32_MAX),
|
||
mRequestMode(RequestMode::No_cors),
|
||
mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW),
|
||
mLastRedirectFlags(0),
|
||
mPriority(PRIORITY_NORMAL),
|
||
mRedirectionLimit(gHttpHandler->RedirectionLimit()),
|
||
mRedirectCount(0),
|
||
mInternalRedirectCount(0),
|
||
mCachedOpaqueResponseBlockingPref(
|
||
StaticPrefs::browser_opaqueResponseBlocking()),
|
||
mChannelBlockedByOpaqueResponse(false),
|
||
mDummyChannelForCachedResource(false),
|
||
mHasContentDecompressed(false),
|
||
mRenderBlocking(false) {
|
||
StoreApplyConversion(true);
|
||
StoreAllowSTS(true);
|
||
StoreTracingEnabled(true);
|
||
StoreReportTiming(true);
|
||
StoreAllowSpdy(true);
|
||
StoreAllowHttp3(true);
|
||
StoreAllowAltSvc(true);
|
||
StoreResponseTimeoutEnabled(true);
|
||
StoreAllRedirectsSameOrigin(true);
|
||
StoreAllRedirectsPassTimingAllowCheck(true);
|
||
StoreUpgradableToSecure(true);
|
||
StoreIsUserAgentHeaderModified(false);
|
||
|
||
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(mPrincipal.forget());
|
||
arrayToRelease.AppendElement(mListener.forget());
|
||
arrayToRelease.AppendElement(mCompressListener.forget());
|
||
arrayToRelease.AppendElement(mORB.forget());
|
||
|
||
if (LoadAddedAsNonTailRequest()) {
|
||
// 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;
|
||
}
|
||
}
|
||
|
||
static bool isSecureOrTrustworthyURL(nsIURI* aURI) {
|
||
return aURI->SchemeIs("https") ||
|
||
(StaticPrefs::network_http_encoding_trustworthy_is_https() &&
|
||
nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(aURI));
|
||
}
|
||
|
||
nsresult HttpBaseChannel::Init(nsIURI* aURI, uint32_t aCaps,
|
||
nsProxyInfo* aProxyInfo,
|
||
uint32_t aProxyResolveFlags, nsIURI* aProxyURI,
|
||
uint64_t aChannelId,
|
||
ExtContentPolicyType aContentPolicyType,
|
||
nsILoadInfo* aLoadInfo) {
|
||
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;
|
||
mLoadInfo = aLoadInfo;
|
||
|
||
// Construct connection info object
|
||
nsAutoCString host;
|
||
int32_t port = -1;
|
||
bool isHTTPS = isSecureOrTrustworthyURL(mURI);
|
||
|
||
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,
|
||
nsContentUtils::ShouldResistFingerprinting(this,
|
||
RFPTarget::HttpUserAgent));
|
||
if (NS_FAILED(rv)) return rv;
|
||
|
||
nsAutoCString type;
|
||
if (aProxyInfo && NS_SUCCEEDED(aProxyInfo->GetType(type)) &&
|
||
!type.EqualsLiteral("unknown")) {
|
||
mProxyInfo = aProxyInfo;
|
||
}
|
||
|
||
mCurrentThread = GetCurrentSerialEventTarget();
|
||
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 = LoadIsPending() || LoadForcePending();
|
||
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 = do_AddRef(mLoadGroup).take();
|
||
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) {
|
||
if (!LoadIsOCSP()) {
|
||
return GetTRRModeImpl(aTRRMode);
|
||
}
|
||
|
||
nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
|
||
nsIDNSService::ResolverMode trrMode = nsIDNSService::MODE_NATIVEONLY;
|
||
// If this is an OCSP channel, and the global TRR mode is TRR_ONLY (3)
|
||
// then we set the mode for this channel as TRR_DISABLED_MODE.
|
||
// We do this to prevent a TRR service channel's OCSP validation from
|
||
// blocking DNS resolution completely.
|
||
if (dns && NS_SUCCEEDED(dns->GetCurrentTrrMode(&trrMode)) &&
|
||
trrMode == nsIDNSService::MODE_TRRONLY) {
|
||
*aTRRMode = nsIRequest::TRR_DISABLED_MODE;
|
||
return NS_OK;
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
nsAutoString customUserAgent;
|
||
bc->GetCustomUserAgent(customUserAgent);
|
||
if (customUserAgent.IsEmpty() || customUserAgent.IsVoid()) {
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_ConvertUTF16toUTF8 utf8CustomUserAgent(customUserAgent);
|
||
nsresult rv = SetRequestHeaderInternal(
|
||
"User-Agent"_ns, utf8CustomUserAgent, false,
|
||
nsHttpHeaderArray::eVarietyRequestEnforceDefault);
|
||
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 = do_AddRef(mOwner).take();
|
||
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) {
|
||
*aLoadInfo = do_AddRef(mLoadInfo).take();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetIsDocument(bool* aIsDocument) {
|
||
return NS_GetIsDocumentChannel(this, aIsDocument);
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
|
||
*aCallbacks = do_AddRef(mCallbacks).take();
|
||
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 || LoadWasOpened() || mDummyChannelForCachedResource) {
|
||
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) {
|
||
// See bug 1658877. If mContentDispositionHint is already
|
||
// DISPOSITION_ATTACHMENT, it means this channel is created from a
|
||
// download attribute. In this case, we should prefer the value from the
|
||
// download attribute rather than the value in content disposition header.
|
||
// DISPOSITION_FORCE_INLINE is used to explicitly set inline, used by
|
||
// the pdf reader when loading a attachment pdf without having to
|
||
// download it.
|
||
if (mContentDispositionHint == nsIChannel::DISPOSITION_ATTACHMENT ||
|
||
mContentDispositionHint == nsIChannel::DISPOSITION_FORCE_INLINE) {
|
||
*aContentDisposition = mContentDispositionHint;
|
||
return NS_OK;
|
||
}
|
||
|
||
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_SUCCEEDED(rv)) {
|
||
rv = NS_GetFilenameFromDisposition(aContentDispositionFilename, header);
|
||
}
|
||
|
||
// If we failed to get the filename from header, we should use
|
||
// mContentDispositionFilename, since mContentDispositionFilename is set from
|
||
// the download attribute.
|
||
if (NS_FAILED(rv)) {
|
||
if (!mContentDispositionFilename) {
|
||
return rv;
|
||
}
|
||
|
||
aContentDispositionFilename = *mContentDispositionFilename;
|
||
return NS_OK;
|
||
}
|
||
|
||
return rv;
|
||
}
|
||
|
||
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 (LoadDeliveringAltData()) {
|
||
MOZ_ASSERT(!mAvailableCachedAltDataType.IsEmpty());
|
||
*aContentLength = mAltDataLength;
|
||
return NS_OK;
|
||
}
|
||
|
||
*aContentLength = mResponseHead->ContentLength();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetContentLength(int64_t value) {
|
||
if (!mDummyChannelForCachedResource) {
|
||
MOZ_ASSERT_UNREACHABLE("HttpBaseChannel::SetContentLength");
|
||
return NS_ERROR_NOT_IMPLEMENTED;
|
||
}
|
||
MOZ_ASSERT(mResponseHead);
|
||
mResponseHead->SetContentLength(value);
|
||
return NS_OK;
|
||
}
|
||
|
||
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(!LoadWasOpened(), 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 = do_AddRef(mUploadStream).take();
|
||
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.
|
||
StoreUploadStreamHasHeaders(false);
|
||
SetRequestMethod("GET"_ns); // revert to GET request
|
||
mUploadStream = nullptr;
|
||
return NS_OK;
|
||
}
|
||
|
||
namespace {
|
||
|
||
class MIMEHeaderCopyVisitor final : public nsIHttpHeaderVisitor {
|
||
public:
|
||
explicit MIMEHeaderCopyVisitor(nsIMIMEInputStream* aDest) : mDest(aDest) {}
|
||
|
||
NS_DECL_ISUPPORTS
|
||
NS_IMETHOD VisitHeader(const nsACString& aName,
|
||
const nsACString& aValue) override {
|
||
return mDest->AddHeader(PromiseFlatCString(aName).get(),
|
||
PromiseFlatCString(aValue).get());
|
||
}
|
||
|
||
private:
|
||
~MIMEHeaderCopyVisitor() = default;
|
||
|
||
nsCOMPtr<nsIMIMEInputStream> mDest;
|
||
};
|
||
|
||
NS_IMPL_ISUPPORTS(MIMEHeaderCopyVisitor, nsIHttpHeaderVisitor)
|
||
|
||
static void NormalizeCopyComplete(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
|
||
|
||
RefPtr<GenericPromise::Private> ready =
|
||
already_AddRefed(static_cast<GenericPromise::Private*>(aClosure));
|
||
if (NS_SUCCEEDED(aStatus)) {
|
||
ready->Resolve(true, __func__);
|
||
} else {
|
||
ready->Reject(aStatus, __func__);
|
||
}
|
||
}
|
||
|
||
// Normalize the upload stream for a HTTP channel, so that is one of the
|
||
// expected and compatible types. Components like WebExtensions and DevTools
|
||
// expect that upload streams in the parent process are cloneable, seekable, and
|
||
// synchronous to read, which this function helps guarantee somewhat efficiently
|
||
// and without loss of information.
|
||
//
|
||
// If the replacement stream outparameter is not initialized to `nullptr`, the
|
||
// returned stream should be used instead of `aUploadStream` as the upload
|
||
// stream for the HTTP channel, and the previous stream should not be touched
|
||
// again.
|
||
//
|
||
// If aReadyPromise is non-nullptr after the function is called, it is a promise
|
||
// which should be awaited before continuing to `AsyncOpen` the HTTP channel,
|
||
// as the replacement stream will not be ready until it is resolved.
|
||
static nsresult NormalizeUploadStream(nsIInputStream* aUploadStream,
|
||
nsIInputStream** aReplacementStream,
|
||
GenericPromise** aReadyPromise) {
|
||
MOZ_ASSERT(XRE_IsParentProcess());
|
||
|
||
*aReplacementStream = nullptr;
|
||
*aReadyPromise = nullptr;
|
||
|
||
// Unwrap RemoteLazyInputStream and normalize the contents as we're in the
|
||
// parent process.
|
||
if (nsCOMPtr<mozIRemoteLazyInputStream> lazyStream =
|
||
do_QueryInterface(aUploadStream)) {
|
||
nsCOMPtr<nsIInputStream> internal;
|
||
if (NS_SUCCEEDED(
|
||
lazyStream->TakeInternalStream(getter_AddRefs(internal)))) {
|
||
nsCOMPtr<nsIInputStream> replacement;
|
||
nsresult rv = NormalizeUploadStream(internal, getter_AddRefs(replacement),
|
||
aReadyPromise);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
||
if (replacement) {
|
||
replacement.forget(aReplacementStream);
|
||
} else {
|
||
internal.forget(aReplacementStream);
|
||
}
|
||
return NS_OK;
|
||
}
|
||
}
|
||
|
||
// Preserve MIME information on the stream when normalizing.
|
||
if (nsCOMPtr<nsIMIMEInputStream> mime = do_QueryInterface(aUploadStream)) {
|
||
nsCOMPtr<nsIInputStream> data;
|
||
nsresult rv = mime->GetData(getter_AddRefs(data));
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
||
nsCOMPtr<nsIInputStream> replacement;
|
||
rv =
|
||
NormalizeUploadStream(data, getter_AddRefs(replacement), aReadyPromise);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
||
if (replacement) {
|
||
nsCOMPtr<nsIMIMEInputStream> replacementMime(
|
||
do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv));
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
||
nsCOMPtr<nsIHttpHeaderVisitor> visitor =
|
||
new MIMEHeaderCopyVisitor(replacementMime);
|
||
rv = mime->VisitHeaders(visitor);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
||
rv = replacementMime->SetData(replacement);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
||
replacementMime.forget(aReplacementStream);
|
||
}
|
||
return NS_OK;
|
||
}
|
||
|
||
// Preserve "real" buffered input streams which wrap data (i.e. are backed by
|
||
// nsBufferedInputStream), but normalize the wrapped stream.
|
||
if (nsCOMPtr<nsIBufferedInputStream> buffered =
|
||
do_QueryInterface(aUploadStream)) {
|
||
nsCOMPtr<nsIInputStream> data;
|
||
if (NS_SUCCEEDED(buffered->GetData(getter_AddRefs(data)))) {
|
||
nsCOMPtr<nsIInputStream> replacement;
|
||
nsresult rv = NormalizeUploadStream(data, getter_AddRefs(replacement),
|
||
aReadyPromise);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
if (replacement) {
|
||
// This buffer size should be kept in sync with HTMLFormSubmission.
|
||
rv = NS_NewBufferedInputStream(aReplacementStream, replacement.forget(),
|
||
8192);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
}
|
||
return NS_OK;
|
||
}
|
||
}
|
||
|
||
// Preserve multiplex input streams, normalizing each individual inner stream
|
||
// to avoid unnecessary copying.
|
||
if (nsCOMPtr<nsIMultiplexInputStream> multiplex =
|
||
do_QueryInterface(aUploadStream)) {
|
||
uint32_t count = multiplex->GetCount();
|
||
nsTArray<nsCOMPtr<nsIInputStream>> streams(count);
|
||
nsTArray<RefPtr<GenericPromise>> promises(count);
|
||
bool replace = false;
|
||
for (uint32_t i = 0; i < count; ++i) {
|
||
nsCOMPtr<nsIInputStream> inner;
|
||
nsresult rv = multiplex->GetStream(i, getter_AddRefs(inner));
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
||
RefPtr<GenericPromise> promise;
|
||
nsCOMPtr<nsIInputStream> replacement;
|
||
rv = NormalizeUploadStream(inner, getter_AddRefs(replacement),
|
||
getter_AddRefs(promise));
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
if (promise) {
|
||
promises.AppendElement(promise);
|
||
}
|
||
if (replacement) {
|
||
streams.AppendElement(replacement);
|
||
replace = true;
|
||
} else {
|
||
streams.AppendElement(inner);
|
||
}
|
||
}
|
||
|
||
// If any of the inner streams needed to be replaced, replace the entire
|
||
// nsIMultiplexInputStream.
|
||
if (replace) {
|
||
nsresult rv;
|
||
nsCOMPtr<nsIMultiplexInputStream> replacement =
|
||
do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
for (auto& stream : streams) {
|
||
rv = replacement->AppendStream(stream);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
}
|
||
|
||
MOZ_ALWAYS_SUCCEEDS(CallQueryInterface(replacement, aReplacementStream));
|
||
}
|
||
|
||
// Wait for all inner promises to settle before resolving the final promise.
|
||
if (!promises.IsEmpty()) {
|
||
RefPtr<GenericPromise> ready =
|
||
GenericPromise::AllSettled(GetCurrentSerialEventTarget(), promises)
|
||
->Then(GetCurrentSerialEventTarget(), __func__,
|
||
[](GenericPromise::AllSettledPromiseType::
|
||
ResolveOrRejectValue&& aResults)
|
||
-> RefPtr<GenericPromise> {
|
||
MOZ_ASSERT(aResults.IsResolve(),
|
||
"AllSettled never rejects");
|
||
for (auto& result : aResults.ResolveValue()) {
|
||
if (result.IsReject()) {
|
||
return GenericPromise::CreateAndReject(
|
||
result.RejectValue(), __func__);
|
||
}
|
||
}
|
||
return GenericPromise::CreateAndResolve(true, __func__);
|
||
});
|
||
ready.forget(aReadyPromise);
|
||
}
|
||
return NS_OK;
|
||
}
|
||
|
||
// If the stream is cloneable, seekable and non-async, we can allow it. Async
|
||
// input streams can cause issues, as various consumers of input streams
|
||
// expect the payload to be synchronous and `Available()` to be the length of
|
||
// the stream, which is not true for asynchronous streams.
|
||
nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(aUploadStream);
|
||
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aUploadStream);
|
||
if (NS_InputStreamIsCloneable(aUploadStream) && seekable && !async) {
|
||
return NS_OK;
|
||
}
|
||
|
||
// Asynchronously copy our non-normalized stream into a StorageStream so that
|
||
// it is seekable, cloneable, and synchronous once the copy completes.
|
||
|
||
NS_WARNING("Upload Stream is being copied into StorageStream");
|
||
|
||
nsCOMPtr<nsIStorageStream> storageStream;
|
||
nsresult rv =
|
||
NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storageStream));
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
||
nsCOMPtr<nsIOutputStream> sink;
|
||
rv = storageStream->GetOutputStream(0, getter_AddRefs(sink));
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
||
nsCOMPtr<nsIInputStream> replacementStream;
|
||
rv = storageStream->NewInputStream(0, getter_AddRefs(replacementStream));
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
|
||
// Ensure the source stream is buffered before starting the copy so we can use
|
||
// ReadSegments, as nsStorageStream doesn't implement WriteSegments.
|
||
nsCOMPtr<nsIInputStream> source = aUploadStream;
|
||
if (!NS_InputStreamIsBuffered(aUploadStream)) {
|
||
nsCOMPtr<nsIInputStream> bufferedSource;
|
||
rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedSource),
|
||
source.forget(), 4096);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
source = bufferedSource.forget();
|
||
}
|
||
|
||
// Perform an AsyncCopy into the input stream on the STS.
|
||
nsCOMPtr<nsIEventTarget> target =
|
||
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
|
||
RefPtr<GenericPromise::Private> ready = new GenericPromise::Private(__func__);
|
||
rv = NS_AsyncCopy(source, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS, 4096,
|
||
NormalizeCopyComplete, do_AddRef(ready).take());
|
||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||
ready.get()->Release();
|
||
return rv;
|
||
}
|
||
|
||
replacementStream.forget(aReplacementStream);
|
||
ready.forget(aReadyPromise);
|
||
return NS_OK;
|
||
}
|
||
|
||
} // anonymous namespace
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::CloneUploadStream(int64_t* aContentLength,
|
||
nsIInputStream** aClonedStream) {
|
||
NS_ENSURE_ARG_POINTER(aContentLength);
|
||
NS_ENSURE_ARG_POINTER(aClonedStream);
|
||
*aClonedStream = nullptr;
|
||
|
||
if (!XRE_IsParentProcess()) {
|
||
NS_WARNING("CloneUploadStream is only supported in the parent process");
|
||
return NS_ERROR_NOT_AVAILABLE;
|
||
}
|
||
|
||
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);
|
||
}
|
||
}
|
||
|
||
StoreUploadStreamHasHeaders(aStreamHasHeaders);
|
||
|
||
return InternalSetUploadStream(aStream, aContentLength, !aStreamHasHeaders);
|
||
}
|
||
|
||
nsresult HttpBaseChannel::InternalSetUploadStream(
|
||
nsIInputStream* aUploadStream, int64_t aContentLength,
|
||
bool aSetContentLengthHeader) {
|
||
// If we're not on the main thread, such as for TRR, the content length must
|
||
// be provided, as we can't normalize our upload stream.
|
||
if (!NS_IsMainThread()) {
|
||
if (aContentLength < 0) {
|
||
MOZ_ASSERT_UNREACHABLE(
|
||
"Upload content length must be explicit off-main-thread");
|
||
return NS_ERROR_INVALID_ARG;
|
||
}
|
||
|
||
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aUploadStream);
|
||
if (!NS_InputStreamIsCloneable(aUploadStream) || !seekable) {
|
||
MOZ_ASSERT_UNREACHABLE(
|
||
"Upload stream must be cloneable & seekable off-main-thread");
|
||
return NS_ERROR_INVALID_ARG;
|
||
}
|
||
|
||
mUploadStream = aUploadStream;
|
||
ExplicitSetUploadStreamLength(aContentLength, aSetContentLengthHeader);
|
||
return NS_OK;
|
||
}
|
||
|
||
// Normalize the upload stream we're provided to ensure that it is cloneable,
|
||
// seekable, and synchronous when in the parent process.
|
||
//
|
||
// This might be an async operation, in which case ready will be returned and
|
||
// resolved when the operation is complete.
|
||
nsCOMPtr<nsIInputStream> replacement;
|
||
RefPtr<GenericPromise> ready;
|
||
if (XRE_IsParentProcess()) {
|
||
nsresult rv = NormalizeUploadStream(
|
||
aUploadStream, getter_AddRefs(replacement), getter_AddRefs(ready));
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
}
|
||
|
||
mUploadStream = replacement ? replacement.get() : aUploadStream;
|
||
|
||
// Once the upload stream is ready, fetch its length before proceeding with
|
||
// AsyncOpen.
|
||
auto onReady = [self = RefPtr{this}, aContentLength, aSetContentLengthHeader,
|
||
stream = mUploadStream]() {
|
||
auto setLengthAndResume = [self, aSetContentLengthHeader](int64_t aLength) {
|
||
self->StorePendingUploadStreamNormalization(false);
|
||
self->ExplicitSetUploadStreamLength(aLength >= 0 ? aLength : 0,
|
||
aSetContentLengthHeader);
|
||
self->MaybeResumeAsyncOpen();
|
||
};
|
||
|
||
if (aContentLength >= 0) {
|
||
setLengthAndResume(aContentLength);
|
||
return;
|
||
}
|
||
|
||
int64_t length;
|
||
if (InputStreamLengthHelper::GetSyncLength(stream, &length)) {
|
||
setLengthAndResume(length);
|
||
return;
|
||
}
|
||
|
||
InputStreamLengthHelper::GetAsyncLength(stream, setLengthAndResume);
|
||
};
|
||
StorePendingUploadStreamNormalization(true);
|
||
|
||
// Resolve onReady synchronously unless a promise is returned.
|
||
if (ready) {
|
||
ready->Then(GetCurrentSerialEventTarget(), __func__,
|
||
[onReady = std::move(onReady)](
|
||
GenericPromise::ResolveOrRejectValue&&) { onReady(); });
|
||
} else {
|
||
onReady();
|
||
}
|
||
return NS_OK;
|
||
}
|
||
|
||
void HttpBaseChannel::ExplicitSetUploadStreamLength(
|
||
uint64_t aContentLength, bool aSetContentLengthHeader) {
|
||
// We already have the content length. We don't need to determinate it.
|
||
mReqContentLength = aContentLength;
|
||
|
||
if (!aSetContentLengthHeader) {
|
||
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;
|
||
}
|
||
|
||
nsAutoCString contentLengthStr;
|
||
contentLengthStr.AppendInt(aContentLength);
|
||
SetRequestHeader(header, contentLengthStr, false);
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetUploadStreamHasHeaders(bool* hasHeaders) {
|
||
NS_ENSURE_ARG(hasHeaders);
|
||
|
||
*hasHeaders = LoadUploadStreamHasHeaders();
|
||
return NS_OK;
|
||
}
|
||
|
||
bool HttpBaseChannel::MaybeWaitForUploadStreamNormalization(
|
||
nsIStreamListener* aListener, nsISupports* aContext) {
|
||
MOZ_ASSERT(NS_IsMainThread());
|
||
MOZ_ASSERT(!LoadAsyncOpenWaitingForStreamNormalization(),
|
||
"AsyncOpen() called twice?");
|
||
|
||
if (!LoadPendingUploadStreamNormalization()) {
|
||
return false;
|
||
}
|
||
|
||
mListener = aListener;
|
||
StoreAsyncOpenWaitingForStreamNormalization(true);
|
||
return true;
|
||
}
|
||
|
||
void HttpBaseChannel::MaybeResumeAsyncOpen() {
|
||
MOZ_ASSERT(NS_IsMainThread());
|
||
MOZ_ASSERT(!LoadPendingUploadStreamNormalization());
|
||
|
||
if (!LoadAsyncOpenWaitingForStreamNormalization()) {
|
||
return;
|
||
}
|
||
|
||
nsCOMPtr<nsIStreamListener> listener;
|
||
listener.swap(mListener);
|
||
|
||
StoreAsyncOpenWaitingForStreamNormalization(false);
|
||
|
||
nsresult rv = AsyncOpen(listener);
|
||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||
DoAsyncAbort(rv);
|
||
}
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// HttpBaseChannel::nsIEncodedChannel
|
||
//-----------------------------------------------------------------------------
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetApplyConversion(bool* value) {
|
||
*value = LoadApplyConversion();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetApplyConversion(bool value) {
|
||
LOG(("HttpBaseChannel::SetApplyConversion [this=%p value=%d]\n", this,
|
||
value));
|
||
StoreApplyConversion(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 nsIThreadRetargetableStreamListener {
|
||
virtual ~InterceptFailedOnStop() = default;
|
||
nsCOMPtr<nsIStreamListener> mNext;
|
||
HttpBaseChannel* mChannel;
|
||
|
||
public:
|
||
InterceptFailedOnStop(nsIStreamListener* arg, HttpBaseChannel* chan)
|
||
: mNext(arg), mChannel(chan) {}
|
||
NS_DECL_THREADSAFE_ISUPPORTS
|
||
NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
|
||
|
||
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_ADDREF(InterceptFailedOnStop)
|
||
NS_IMPL_RELEASE(InterceptFailedOnStop)
|
||
|
||
NS_INTERFACE_MAP_BEGIN(InterceptFailedOnStop)
|
||
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
|
||
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
|
||
NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
|
||
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver)
|
||
NS_INTERFACE_MAP_END
|
||
|
||
NS_IMETHODIMP
|
||
InterceptFailedOnStop::CheckListenerChain() {
|
||
nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
|
||
do_QueryInterface(mNext);
|
||
if (!listener) {
|
||
return NS_ERROR_NO_INTERFACE;
|
||
}
|
||
|
||
return listener->CheckListenerChain();
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
InterceptFailedOnStop::OnDataFinished(nsresult aStatus) {
|
||
nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
|
||
do_QueryInterface(mNext);
|
||
if (listener) {
|
||
return listener->OnDataFinished(aStatus);
|
||
}
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
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 (!LoadApplyConversion()) {
|
||
LOG(("not applying conversion per ApplyConversion\n"));
|
||
return NS_OK;
|
||
}
|
||
|
||
if (LoadHasAppliedConversion()) {
|
||
LOG(("not applying conversion because HasAppliedConversion is true\n"));
|
||
return NS_OK;
|
||
}
|
||
|
||
if (LoadDeliveringAltData()) {
|
||
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,
|
||
isSecureOrTrustworthyURL(mURI))) {
|
||
RefPtr<nsHTTPCompressConv> converter = new nsHTTPCompressConv();
|
||
nsAutoCString from(val);
|
||
ToLowerCase(from);
|
||
rv = converter->AsyncConvertData(from.get(), "uncompressed", nextListener,
|
||
aCtxt);
|
||
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;
|
||
} else if (from.EqualsLiteral("zstd")) {
|
||
mode = 4;
|
||
}
|
||
glean::http::content_encoding.AccumulateSingleSample(mode);
|
||
}
|
||
nextListener = converter;
|
||
} else {
|
||
if (val) LOG(("Unknown content encoding '%s', ignoring\n", val));
|
||
}
|
||
}
|
||
*aNewNextListener = do_AddRef(nextListener).take();
|
||
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;
|
||
}
|
||
}
|
||
|
||
if (!haveType) {
|
||
encoding.BeginReading(start);
|
||
if (CaseInsensitiveFindInReadable("zstd"_ns, start, end)) {
|
||
aNextEncoding.AssignLiteral(APPLICATION_ZSTD);
|
||
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));
|
||
if (topWindow) {
|
||
if (nsPIDOMWindowInner* inner =
|
||
nsPIDOMWindowOuter::From(topWindow)->GetCurrentInnerWindow()) {
|
||
mContentWindowId = inner->WindowID();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
*aWindowId = mContentWindowId;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP HttpBaseChannel::SetBrowserId(uint64_t aId) {
|
||
mBrowserId = aId;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP HttpBaseChannel::GetBrowserId(uint64_t* aId) {
|
||
EnsureBrowserId();
|
||
*aId = mBrowserId;
|
||
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,
|
||
mLoadInfo->GetOriginAttributes().IsPrivateBrowsing());
|
||
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::GetTransferSize(uint64_t* aTransferSize) {
|
||
MutexAutoLock lock(mOnDataFinishedMutex);
|
||
*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) {
|
||
MutexAutoLock lock(mOnDataFinishedMutex);
|
||
*aEncodedBodySize = mEncodedBodySize;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetSupportsHTTP3(bool* aSupportsHTTP3) {
|
||
*aSupportsHTTP3 = mSupportsHTTP3;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetHasHTTPSRR(bool* aHasHTTPSRR) {
|
||
*aHasHTTPSRR = LoadHasHTTPSRR();
|
||
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();
|
||
|
||
mLoadInfo->SetIsGETRequest(aMethod.Equals("GET"));
|
||
|
||
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 (aClone) {
|
||
// Record the telemetry once we set the referrer info to the channel
|
||
// successfully.
|
||
referrerInfo->RecordTelemetry(this);
|
||
}
|
||
|
||
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) {
|
||
return SetRequestHeaderInternal(aHeader, aValue, aMerge,
|
||
nsHttpHeaderArray::eVarietyRequestOverride);
|
||
}
|
||
|
||
nsresult HttpBaseChannel::SetRequestHeaderInternal(
|
||
const nsACString& aHeader, const nsACString& aValue, bool aMerge,
|
||
nsHttpHeaderArray::HeaderVariety aVariety) {
|
||
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;
|
||
}
|
||
|
||
// Mark that the User-Agent header has been modified.
|
||
if (nsHttp::ResolveAtom(aHeader) == nsHttp::User_Agent) {
|
||
StoreIsUserAgentHeaderModified(true);
|
||
}
|
||
|
||
return mRequestHead.SetHeader(aHeader, flatValue, aMerge);
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetNewReferrerInfo(const nsACString& aUrl,
|
||
nsIReferrerInfo::ReferrerPolicyIDL aPolicy,
|
||
bool aSendReferrer) {
|
||
nsresult rv;
|
||
// Create URI from string
|
||
nsCOMPtr<nsIURI> aURI;
|
||
rv = NS_NewURI(getter_AddRefs(aURI), aUrl);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
// Create new ReferrerInfo and initialize it.
|
||
nsCOMPtr<nsIReferrerInfo> referrerInfo = new mozilla::dom::ReferrerInfo();
|
||
rv = referrerInfo->Init(aPolicy, aSendReferrer, aURI);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
// Set ReferrerInfo
|
||
return SetReferrerInfo(referrerInfo);
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
// Mark that the User-Agent header has been modified.
|
||
if (nsHttp::ResolveAtom(aHeader) == nsHttp::User_Agent) {
|
||
StoreIsUserAgentHeaderModified(true);
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
StoreResponseHeadersModified(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::GetAllowSTS(bool* value) {
|
||
NS_ENSURE_ARG_POINTER(value);
|
||
*value = LoadAllowSTS();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetAllowSTS(bool value) {
|
||
ENSURE_CALLED_BEFORE_CONNECT();
|
||
StoreAllowSTS(value);
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetIsOCSP(bool* value) {
|
||
NS_ENSURE_ARG_POINTER(value);
|
||
*value = LoadIsOCSP();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetIsOCSP(bool value) {
|
||
ENSURE_CALLED_BEFORE_CONNECT();
|
||
StoreIsOCSP(value);
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetIsUserAgentHeaderModified(bool* value) {
|
||
NS_ENSURE_ARG_POINTER(value);
|
||
*value = LoadIsUserAgentHeaderModified();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetIsUserAgentHeaderModified(bool value) {
|
||
StoreIsUserAgentHeaderModified(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(
|
||
nsITransportSecurityInfo* 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(LoadResponseCouldBeSynthesized(),
|
||
"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 (!LoadResponseCouldBeSynthesized()) {
|
||
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;
|
||
nsAutoCString version;
|
||
// https://fetch.spec.whatwg.org :
|
||
// Responses over an HTTP/2 connection will always have the empty byte
|
||
// sequence as status message as HTTP/2 does not support them.
|
||
if (NS_WARN_IF(NS_FAILED(GetProtocolVersion(version))) ||
|
||
!version.EqualsLiteral("h2")) {
|
||
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(LoadOnStartRequestCalled(), NS_ERROR_NOT_AVAILABLE);
|
||
|
||
// The first parameter is the URI we would like to redirect to
|
||
// The second parameter should default to false if normal redirect
|
||
mAPIRedirectTo =
|
||
Some(mozilla::MakeCompactPair(nsCOMPtr<nsIURI>(targetURI), false));
|
||
|
||
// 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::TransparentRedirectTo(nsIURI* targetURI) {
|
||
LOG(("HttpBaseChannel::TransparentRedirectTo [this=%p]", this));
|
||
RedirectTo(targetURI);
|
||
MOZ_ASSERT(mAPIRedirectTo, "How did this happen?");
|
||
mAPIRedirectTo->second() = true;
|
||
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(LoadUpgradableToSecure(), NS_ERROR_NOT_AVAILABLE);
|
||
|
||
StoreUpgradeToSecure(true);
|
||
// todo: Currently UpgradeToSecure() is called only by web extensions, if
|
||
// that ever changes, we need to update the following telemetry collection
|
||
// to reflect any future changes.
|
||
mLoadInfo->SetHttpsUpgradeTelemetry(nsILoadInfo::WEB_EXTENSION_UPGRADE);
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetRequestObserversCalled(bool* aCalled) {
|
||
NS_ENSURE_ARG_POINTER(aCalled);
|
||
*aCalled = LoadRequestObserversCalled();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetRequestObserversCalled(bool aCalled) {
|
||
StoreRequestObserversCalled(aCalled);
|
||
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) {
|
||
StoreForceMainDocumentChannel(aValue);
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetProtocolVersion(nsACString& aProtocolVersion) {
|
||
// Try to use ALPN if available and if it is not for a proxy, i.e if an
|
||
// https proxy was not used or if https proxy was used but the connection to
|
||
// the origin server is also https. In the case, an https proxy was used and
|
||
// the connection to the origin server was http, mSecurityInfo will be from
|
||
// the proxy.
|
||
if (!mConnectionInfo || !mConnectionInfo->UsingHttpsProxy() ||
|
||
mConnectionInfo->EndToEndSSL()) {
|
||
nsAutoCString protocol;
|
||
if (mSecurityInfo &&
|
||
NS_SUCCEEDED(mSecurityInfo->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 = components::ThirdPartyUtil::Service();
|
||
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
|
||
}
|
||
}
|
||
*aTopWindowURI = do_AddRef(mTopWindowURI).take();
|
||
return rv;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetDocumentURI(nsIURI** aDocumentURI) {
|
||
NS_ENSURE_ARG_POINTER(aDocumentURI);
|
||
*aDocumentURI = do_AddRef(mDocumentURI).take();
|
||
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;
|
||
}
|
||
|
||
bool HttpBaseChannel::IsBrowsingContextDiscarded() const {
|
||
// If there is no loadGroup attached to the current channel, we check the
|
||
// global private browsing state for the private channel instead. For
|
||
// non-private channel, we will always return false here.
|
||
//
|
||
// Note that we can only access the global private browsing state in the
|
||
// parent process. So, we will fallback to just return false in the content
|
||
// process.
|
||
if (!mLoadGroup) {
|
||
if (!XRE_IsParentProcess()) {
|
||
return false;
|
||
}
|
||
|
||
return mLoadInfo->GetOriginAttributes().IsPrivateBrowsing() &&
|
||
!dom::CanonicalBrowsingContext::IsPrivateBrowsingActive();
|
||
}
|
||
|
||
return mLoadGroup->GetIsBrowsingContextDiscarded();
|
||
}
|
||
|
||
// 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() !=
|
||
ExtContentPolicy::TYPE_DOCUMENT &&
|
||
mLoadInfo->GetExternalContentPolicyType() !=
|
||
ExtContentPolicy::TYPE_SUBDOCUMENT) {
|
||
return NS_OK;
|
||
}
|
||
|
||
nsILoadInfo::CrossOriginEmbedderPolicy resultPolicy =
|
||
nsILoadInfo::EMBEDDER_POLICY_NULL;
|
||
bool isCoepCredentiallessEnabled;
|
||
rv = mLoadInfo->GetIsOriginTrialCoepCredentiallessEnabledForTopLevel(
|
||
&isCoepCredentiallessEnabled);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
rv = GetResponseEmbedderPolicy(isCoepCredentiallessEnabled, &resultPolicy);
|
||
if (NS_FAILED(rv)) {
|
||
return NS_OK;
|
||
}
|
||
|
||
// https://html.spec.whatwg.org/multipage/origin.html#coep
|
||
if (mLoadInfo->GetExternalContentPolicyType() ==
|
||
ExtContentPolicy::TYPE_SUBDOCUMENT &&
|
||
!nsHttpChannel::IsRedirectStatus(mResponseHead->Status()) &&
|
||
mLoadInfo->GetLoadingEmbedderPolicy() !=
|
||
nsILoadInfo::EMBEDDER_POLICY_NULL &&
|
||
resultPolicy != nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP &&
|
||
resultPolicy != nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS) {
|
||
return NS_ERROR_DOM_COEP_FAILED;
|
||
}
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
// https://mikewest.github.io/corpp/#corp-check
|
||
nsresult HttpBaseChannel::ProcessCrossOriginResourcePolicyHeader() {
|
||
// Fetch 4.5.9
|
||
dom::RequestMode requestMode;
|
||
MOZ_ALWAYS_SUCCEEDS(GetRequestMode(&requestMode));
|
||
// XXX this seems wrong per spec? What about navigate
|
||
if (requestMode != RequestMode::No_cors) {
|
||
return NS_OK;
|
||
}
|
||
|
||
// We only apply this for resources.
|
||
auto extContentPolicyType = mLoadInfo->GetExternalContentPolicyType();
|
||
if (extContentPolicyType == ExtContentPolicy::TYPE_DOCUMENT ||
|
||
extContentPolicyType == ExtContentPolicy::TYPE_WEBSOCKET ||
|
||
extContentPolicyType == ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) {
|
||
return NS_OK;
|
||
}
|
||
|
||
if (extContentPolicyType == ExtContentPolicy::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;
|
||
}
|
||
|
||
if (mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()) {
|
||
return NS_OK;
|
||
}
|
||
|
||
nsAutoCString content;
|
||
Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Resource_Policy,
|
||
content);
|
||
|
||
if (StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
|
||
if (content.IsEmpty()) {
|
||
if (mLoadInfo->GetLoadingEmbedderPolicy() ==
|
||
nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS) {
|
||
bool requestIncludesCredentials = false;
|
||
nsresult rv = GetCorsIncludeCredentials(&requestIncludesCredentials);
|
||
if (NS_FAILED(rv)) {
|
||
return NS_OK;
|
||
}
|
||
// COEP: Set policy to `same-origin` if: response’s
|
||
// request-includes-credentials is true, or forNavigation is true.
|
||
if (requestIncludesCredentials ||
|
||
extContentPolicyType == ExtContentPolicyType::TYPE_SUBDOCUMENT) {
|
||
content = "same-origin"_ns;
|
||
}
|
||
} else if (mLoadInfo->GetLoadingEmbedderPolicy() ==
|
||
nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP) {
|
||
// 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.
|
||
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() {
|
||
MOZ_ASSERT(XRE_IsParentProcess());
|
||
|
||
StoreHasCrossOriginOpenerPolicyMismatch(false);
|
||
if (!StaticPrefs::browser_tabs_remote_useCrossOriginOpenerPolicy()) {
|
||
return NS_OK;
|
||
}
|
||
|
||
// Only consider Cross-Origin-Opener-Policy for toplevel document loads.
|
||
if (mLoadInfo->GetExternalContentPolicyType() !=
|
||
ExtContentPolicy::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;
|
||
}
|
||
|
||
nsCOMPtr<nsIPrincipal> resultOrigin;
|
||
nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
|
||
this, getter_AddRefs(resultOrigin));
|
||
|
||
// 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;
|
||
|
||
// Add a permission to mark this site as high-value into the permission DB.
|
||
if (resultPolicy != nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) {
|
||
mozilla::dom::AddHighValuePermission(
|
||
resultOrigin, mozilla::dom::kHighValueCOOPPermission);
|
||
}
|
||
|
||
// 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 &&
|
||
mLoadInfo->GetSandboxFlags()) {
|
||
LOG((
|
||
"HttpBaseChannel::ComputeCrossOriginOpenerPolicyMismatch network error "
|
||
"for non empty sandboxing and non null COOP"));
|
||
return NS_ERROR_DOM_COOP_FAILED;
|
||
}
|
||
|
||
// In xpcshell-tests we don't always have a current window global
|
||
RefPtr<mozilla::dom::WindowGlobalParent> currentWindowGlobal =
|
||
ctx->Canonical()->GetCurrentWindowGlobal();
|
||
if (!currentWindowGlobal) {
|
||
return NS_OK;
|
||
}
|
||
|
||
// We use the top window principal as the documentOrigin
|
||
nsCOMPtr<nsIPrincipal> documentOrigin =
|
||
currentWindowGlobal->DocumentPrincipal();
|
||
|
||
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) {
|
||
StoreHasCrossOriginOpenerPolicyMismatch(true);
|
||
return NS_OK;
|
||
}
|
||
|
||
if (resultPolicy != nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) {
|
||
StoreHasCrossOriginOpenerPolicyMismatch(true);
|
||
return NS_OK;
|
||
}
|
||
|
||
if (!currentWindowGlobal->IsInitialDocument()) {
|
||
StoreHasCrossOriginOpenerPolicyMismatch(true);
|
||
return NS_OK;
|
||
}
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
nsresult HttpBaseChannel::ProcessCrossOriginSecurityHeaders() {
|
||
StoreProcessCrossOriginSecurityHeadersCalled(true);
|
||
nsresult rv = ProcessCrossOriginEmbedderPolicyHeader();
|
||
if (NS_FAILED(rv)) {
|
||
return rv;
|
||
}
|
||
rv = ProcessCrossOriginResourcePolicyHeader();
|
||
if (NS_FAILED(rv)) {
|
||
return rv;
|
||
}
|
||
return ComputeCrossOriginOpenerPolicyMismatch();
|
||
}
|
||
|
||
enum class Report { Error, Warning };
|
||
|
||
// Helper Function to report messages to the console when the loaded
|
||
// script had a wrong MIME type.
|
||
void ReportMimeTypeMismatch(HttpBaseChannel* aChannel, const char* aMessageName,
|
||
nsIURI* aURI, const nsACString& aContentType,
|
||
Report report) {
|
||
NS_ConvertUTF8toUTF16 spec(aURI->GetSpecOrDefault());
|
||
NS_ConvertUTF8toUTF16 contentType(aContentType);
|
||
|
||
aChannel->LogMimeTypeMismatch(nsCString(aMessageName),
|
||
report == Report::Warning, spec, contentType);
|
||
}
|
||
|
||
// Check and potentially enforce X-Content-Type-Options: nosniff
|
||
nsresult ProcessXCTO(HttpBaseChannel* aChannel, nsIURI* aURI,
|
||
nsHttpResponseHead* aResponseHead,
|
||
nsILoadInfo* aLoadInfo) {
|
||
if (!aURI || !aResponseHead || !aLoadInfo) {
|
||
// if there is no uri, no response head or no loadInfo, then there is
|
||
// nothing to do
|
||
return NS_OK;
|
||
}
|
||
|
||
// 1) Query the XCTO header and check if 'nosniff' is the first value.
|
||
nsAutoCString contentTypeOptionsHeader;
|
||
if (!aResponseHead->GetContentTypeOptionsHeader(contentTypeOptionsHeader)) {
|
||
// if failed to get XCTO header, then there is nothing to do.
|
||
return NS_OK;
|
||
}
|
||
|
||
// let's compare the header (ignoring case)
|
||
// e.g. "NoSniFF" -> "nosniff"
|
||
// if it's not 'nosniff' then there is nothing to do here
|
||
if (!contentTypeOptionsHeader.EqualsIgnoreCase("nosniff")) {
|
||
// since we are getting here, the XCTO header was sent;
|
||
// a non matching value most likely means a mistake happenend;
|
||
// e.g. sending 'nosnif' instead of 'nosniff', let's log a warning.
|
||
AutoTArray<nsString, 1> params;
|
||
CopyUTF8toUTF16(contentTypeOptionsHeader, *params.AppendElement());
|
||
RefPtr<dom::Document> doc;
|
||
aLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
|
||
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "XCTO"_ns, doc,
|
||
nsContentUtils::eSECURITY_PROPERTIES,
|
||
"XCTOHeaderValueMissing", params);
|
||
return NS_OK;
|
||
}
|
||
|
||
// 2) Query the content type from the channel
|
||
nsAutoCString contentType;
|
||
aResponseHead->ContentType(contentType);
|
||
|
||
// 3) Compare the expected MIME type with the actual type
|
||
if (aLoadInfo->GetExternalContentPolicyType() ==
|
||
ExtContentPolicy::TYPE_STYLESHEET) {
|
||
if (contentType.EqualsLiteral(TEXT_CSS)) {
|
||
return NS_OK;
|
||
}
|
||
ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType,
|
||
Report::Error);
|
||
return NS_ERROR_CORRUPTED_CONTENT;
|
||
}
|
||
|
||
if (aLoadInfo->GetExternalContentPolicyType() ==
|
||
ExtContentPolicy::TYPE_SCRIPT) {
|
||
if (nsContentUtils::IsJavascriptMIMEType(
|
||
NS_ConvertUTF8toUTF16(contentType))) {
|
||
return NS_OK;
|
||
}
|
||
ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType,
|
||
Report::Error);
|
||
return NS_ERROR_CORRUPTED_CONTENT;
|
||
}
|
||
|
||
auto policyType = aLoadInfo->GetExternalContentPolicyType();
|
||
if (policyType == ExtContentPolicy::TYPE_DOCUMENT ||
|
||
policyType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
|
||
// If the header XCTO nosniff is set for any browsing context, then
|
||
// we set the skipContentSniffing flag on the Loadinfo. Within
|
||
// GetMIMETypeFromContent we then bail early and do not do any sniffing.
|
||
aLoadInfo->SetSkipContentSniffing(true);
|
||
return NS_OK;
|
||
}
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
// Ensure that a load of type script has correct MIME type
|
||
nsresult EnsureMIMEOfScript(HttpBaseChannel* aChannel, nsIURI* aURI,
|
||
nsHttpResponseHead* aResponseHead,
|
||
nsILoadInfo* aLoadInfo) {
|
||
if (!aURI || !aResponseHead || !aLoadInfo) {
|
||
// if there is no uri, no response head or no loadInfo, then there is
|
||
// nothing to do
|
||
return NS_OK;
|
||
}
|
||
|
||
if (aLoadInfo->GetExternalContentPolicyType() !=
|
||
ExtContentPolicy::TYPE_SCRIPT) {
|
||
// if this is not a script load, then there is nothing to do
|
||
return NS_OK;
|
||
}
|
||
|
||
nsAutoCString contentType;
|
||
aResponseHead->ContentType(contentType);
|
||
NS_ConvertUTF8toUTF16 typeString(contentType);
|
||
|
||
if (nsContentUtils::IsJavascriptMIMEType(typeString)) {
|
||
// script load has type script
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eJavascript)
|
||
.Add();
|
||
return NS_OK;
|
||
}
|
||
|
||
nsContentPolicyType internalType = aLoadInfo->InternalContentPolicyType();
|
||
bool isModule =
|
||
internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE ||
|
||
internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD;
|
||
|
||
if (isModule && nsContentUtils::IsJsonMimeType(typeString)) {
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eJavascript)
|
||
.Add();
|
||
return NS_OK;
|
||
}
|
||
|
||
switch (aLoadInfo->InternalContentPolicyType()) {
|
||
case nsIContentPolicy::TYPE_SCRIPT:
|
||
case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
|
||
case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
|
||
case nsIContentPolicy::TYPE_INTERNAL_MODULE:
|
||
case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
|
||
case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT:
|
||
case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT:
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eScriptLoad)
|
||
.Add();
|
||
break;
|
||
case nsIContentPolicy::TYPE_INTERNAL_WORKER:
|
||
case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE:
|
||
case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eWorkerLoad)
|
||
.Add();
|
||
break;
|
||
case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(
|
||
glean::http::ScriptBlockIncorrectMimeLabel::eServiceworkerLoad)
|
||
.Add();
|
||
break;
|
||
case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(
|
||
glean::http::ScriptBlockIncorrectMimeLabel::eImportscriptLoad)
|
||
.Add();
|
||
break;
|
||
case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET:
|
||
case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET:
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eWorkletLoad)
|
||
.Add();
|
||
break;
|
||
default:
|
||
MOZ_ASSERT_UNREACHABLE("unexpected script type");
|
||
break;
|
||
}
|
||
|
||
if (aLoadInfo->GetLoadingPrincipal()->IsSameOrigin(aURI)) {
|
||
// same origin
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eSameOrigin)
|
||
.Add();
|
||
} else {
|
||
bool cors = false;
|
||
nsAutoCString corsOrigin;
|
||
nsresult rv = aResponseHead->GetHeader(
|
||
nsHttp::ResolveAtom("Access-Control-Allow-Origin"_ns), corsOrigin);
|
||
if (NS_SUCCEEDED(rv)) {
|
||
if (corsOrigin.Equals("*")) {
|
||
cors = true;
|
||
} else {
|
||
nsCOMPtr<nsIURI> corsOriginURI;
|
||
rv = NS_NewURI(getter_AddRefs(corsOriginURI), corsOrigin);
|
||
if (NS_SUCCEEDED(rv)) {
|
||
if (aLoadInfo->GetLoadingPrincipal()->IsSameOrigin(corsOriginURI)) {
|
||
cors = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (cors) {
|
||
// cors origin
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eCorsOrigin)
|
||
.Add();
|
||
} else {
|
||
// cross origin
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eCrossOrigin)
|
||
.Add();
|
||
}
|
||
}
|
||
|
||
bool block = false;
|
||
if (StringBeginsWith(contentType, "image/"_ns)) {
|
||
// script load has type image
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eImage)
|
||
.Add();
|
||
block = true;
|
||
} else if (StringBeginsWith(contentType, "audio/"_ns)) {
|
||
// script load has type audio
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eAudio)
|
||
.Add();
|
||
block = true;
|
||
} else if (StringBeginsWith(contentType, "video/"_ns)) {
|
||
// script load has type video
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eVideo)
|
||
.Add();
|
||
block = true;
|
||
} else if (StringBeginsWith(contentType, "text/csv"_ns)) {
|
||
// script load has type text/csv
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eTextCsv)
|
||
.Add();
|
||
block = true;
|
||
}
|
||
|
||
if (block) {
|
||
ReportMimeTypeMismatch(aChannel, "BlockScriptWithWrongMimeType2", aURI,
|
||
contentType, Report::Error);
|
||
return NS_ERROR_CORRUPTED_CONTENT;
|
||
}
|
||
|
||
if (StringBeginsWith(contentType, "text/plain"_ns)) {
|
||
// script load has type text/plain
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eTextPlain)
|
||
.Add();
|
||
} else if (StringBeginsWith(contentType, "text/xml"_ns)) {
|
||
// script load has type text/xml
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eTextXml)
|
||
.Add();
|
||
} else if (StringBeginsWith(contentType, "application/octet-stream"_ns)) {
|
||
// script load has type application/octet-stream
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eAppOctetStream)
|
||
.Add();
|
||
} else if (StringBeginsWith(contentType, "application/xml"_ns)) {
|
||
// script load has type application/xml
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eAppXml)
|
||
.Add();
|
||
} else if (StringBeginsWith(contentType, "application/json"_ns)) {
|
||
// script load has type application/json
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eAppJson)
|
||
.Add();
|
||
} else if (StringBeginsWith(contentType, "text/json"_ns)) {
|
||
// script load has type text/json
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eTextJson)
|
||
.Add();
|
||
} else if (StringBeginsWith(contentType, "text/html"_ns)) {
|
||
// script load has type text/html
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eTextHtml)
|
||
.Add();
|
||
} else if (contentType.IsEmpty()) {
|
||
// script load has no type
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eEmpty)
|
||
.Add();
|
||
} else {
|
||
// script load has unknown type
|
||
glean::http::script_block_incorrect_mime
|
||
.EnumGet(glean::http::ScriptBlockIncorrectMimeLabel::eUnknown)
|
||
.Add();
|
||
}
|
||
|
||
// We restrict importScripts() in worker code to JavaScript MIME types.
|
||
if (internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS ||
|
||
internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE) {
|
||
ReportMimeTypeMismatch(aChannel, "BlockImportScriptsWithWrongMimeType",
|
||
aURI, contentType, Report::Error);
|
||
return NS_ERROR_CORRUPTED_CONTENT;
|
||
}
|
||
|
||
if (internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
|
||
internalType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER) {
|
||
// Do not block the load if the feature is not enabled.
|
||
if (!StaticPrefs::security_block_Worker_with_wrong_mime()) {
|
||
return NS_OK;
|
||
}
|
||
|
||
ReportMimeTypeMismatch(aChannel, "BlockWorkerWithWrongMimeType", aURI,
|
||
contentType, Report::Error);
|
||
return NS_ERROR_CORRUPTED_CONTENT;
|
||
}
|
||
|
||
// ES6 modules require a strict MIME type check.
|
||
if (isModule) {
|
||
ReportMimeTypeMismatch(aChannel, "BlockModuleWithWrongMimeType", aURI,
|
||
contentType, Report::Error);
|
||
return NS_ERROR_CORRUPTED_CONTENT;
|
||
}
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
// Warn when a load of type script uses a wrong MIME type and
|
||
// wasn't blocked by EnsureMIMEOfScript or ProcessXCTO.
|
||
void WarnWrongMIMEOfScript(HttpBaseChannel* aChannel, nsIURI* aURI,
|
||
nsHttpResponseHead* aResponseHead,
|
||
nsILoadInfo* aLoadInfo) {
|
||
if (!aURI || !aResponseHead || !aLoadInfo) {
|
||
// If there is no uri, no response head or no loadInfo, then there is
|
||
// nothing to do.
|
||
return;
|
||
}
|
||
|
||
if (aLoadInfo->GetExternalContentPolicyType() !=
|
||
ExtContentPolicy::TYPE_SCRIPT) {
|
||
// If this is not a script load, then there is nothing to do.
|
||
return;
|
||
}
|
||
|
||
bool succeeded;
|
||
MOZ_ALWAYS_SUCCEEDS(aChannel->GetRequestSucceeded(&succeeded));
|
||
if (!succeeded) {
|
||
// Do not warn for failed loads: HTTP error pages are usually in HTML.
|
||
return;
|
||
}
|
||
|
||
nsAutoCString contentType;
|
||
aResponseHead->ContentType(contentType);
|
||
NS_ConvertUTF8toUTF16 typeString(contentType);
|
||
|
||
if (nsContentUtils::IsJavascriptMIMEType(typeString)) {
|
||
return;
|
||
}
|
||
|
||
nsContentPolicyType internalType = aLoadInfo->InternalContentPolicyType();
|
||
bool isModule =
|
||
internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE ||
|
||
internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD;
|
||
if (isModule && nsContentUtils::IsJsonMimeType(typeString)) {
|
||
return;
|
||
}
|
||
|
||
ReportMimeTypeMismatch(aChannel, "WarnScriptWithWrongMimeType", aURI,
|
||
contentType, Report::Warning);
|
||
}
|
||
|
||
nsresult HttpBaseChannel::ValidateMIMEType() {
|
||
nsresult rv = EnsureMIMEOfScript(this, mURI, mResponseHead.get(), mLoadInfo);
|
||
if (NS_FAILED(rv)) {
|
||
return rv;
|
||
}
|
||
|
||
rv = ProcessXCTO(this, mURI, mResponseHead.get(), mLoadInfo);
|
||
if (NS_FAILED(rv)) {
|
||
return rv;
|
||
}
|
||
|
||
WarnWrongMIMEOfScript(this, mURI, mResponseHead.get(), mLoadInfo);
|
||
return NS_OK;
|
||
}
|
||
|
||
bool HttpBaseChannel::ShouldFilterOpaqueResponse(
|
||
OpaqueResponseFilterFetch aFilterType) const {
|
||
MOZ_ASSERT(ShouldBlockOpaqueResponse());
|
||
|
||
if (!mLoadInfo || ConfiguredFilterFetchResponseBehaviour() != aFilterType) {
|
||
return false;
|
||
}
|
||
|
||
// We should filter a response in the parent if it is opaque and is the result
|
||
// of a fetch() function from the Fetch specification.
|
||
return mLoadInfo->InternalContentPolicyType() == nsIContentPolicy::TYPE_FETCH;
|
||
}
|
||
|
||
bool HttpBaseChannel::ShouldBlockOpaqueResponse() const {
|
||
if (!mURI || !mResponseHead || !mLoadInfo) {
|
||
// if there is no uri, no response head or no loadInfo, then there is
|
||
// nothing to do
|
||
LOGORB("No block: no mURI, mResponseHead, or mLoadInfo");
|
||
return false;
|
||
}
|
||
|
||
nsCOMPtr<nsIPrincipal> principal = mLoadInfo->GetLoadingPrincipal();
|
||
if (!principal || principal->IsSystemPrincipal()) {
|
||
// If it's a top-level load or a system principal, then there is nothing to
|
||
// do.
|
||
LOGORB("No block: top-level load or system principal");
|
||
return false;
|
||
}
|
||
|
||
// Check if the response is a opaque response, which means requestMode should
|
||
// be RequestMode::No_cors and responseType should be ResponseType::Opaque.
|
||
nsContentPolicyType contentPolicy = mLoadInfo->InternalContentPolicyType();
|
||
|
||
// Skip the RequestMode would be RequestMode::Navigate
|
||
if (contentPolicy == nsIContentPolicy::TYPE_DOCUMENT ||
|
||
contentPolicy == nsIContentPolicy::TYPE_SUBDOCUMENT ||
|
||
contentPolicy == nsIContentPolicy::TYPE_INTERNAL_FRAME ||
|
||
contentPolicy == nsIContentPolicy::TYPE_INTERNAL_IFRAME ||
|
||
// Skip the RequestMode would be RequestMode::Same_origin
|
||
contentPolicy == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
|
||
contentPolicy == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER) {
|
||
return false;
|
||
}
|
||
|
||
uint32_t securityMode = mLoadInfo->GetSecurityMode();
|
||
// Skip when RequestMode would not be RequestMode::no_cors
|
||
if (securityMode !=
|
||
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT &&
|
||
securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL) {
|
||
LOGORB("No block: not no_cors requests");
|
||
return false;
|
||
}
|
||
|
||
// Only continue when ResponseType would be ResponseType::Opaque
|
||
if (mLoadInfo->GetTainting() != mozilla::LoadTainting::Opaque) {
|
||
LOGORB("No block: not opaque response");
|
||
return false;
|
||
}
|
||
|
||
auto extContentPolicyType = mLoadInfo->GetExternalContentPolicyType();
|
||
if (extContentPolicyType == ExtContentPolicy::TYPE_OBJECT ||
|
||
extContentPolicyType == ExtContentPolicy::TYPE_WEBSOCKET ||
|
||
extContentPolicyType == ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) {
|
||
LOGORB("No block: object || websocket request || save as download");
|
||
return false;
|
||
}
|
||
|
||
// Ignore the request from object or embed elements
|
||
if (mLoadInfo->GetIsFromObjectOrEmbed()) {
|
||
LOGORB("No block: Request From <object> or <embed>");
|
||
return false;
|
||
}
|
||
|
||
// Exclude no_cors System XHR
|
||
if (extContentPolicyType == ExtContentPolicy::TYPE_XMLHTTPREQUEST) {
|
||
if (securityMode ==
|
||
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT) {
|
||
LOGORB("No block: System XHR");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// Exclude no_cors web-identity
|
||
if (extContentPolicyType == ExtContentPolicy::TYPE_WEB_IDENTITY) {
|
||
if (securityMode ==
|
||
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT) {
|
||
printf("Allowing ORB for web-identity\n");
|
||
LOGORB("No block: System web-identity");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
uint32_t httpsOnlyStatus = mLoadInfo->GetHttpsOnlyStatus();
|
||
if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_BYPASS_ORB) {
|
||
LOGORB("No block: HTTPS_ONLY_BYPASS_ORB");
|
||
return false;
|
||
}
|
||
|
||
bool isInDevToolsContext;
|
||
mLoadInfo->GetIsInDevToolsContext(&isInDevToolsContext);
|
||
if (isInDevToolsContext) {
|
||
LOGORB("No block: Request created by devtools");
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
OpaqueResponse HttpBaseChannel::BlockOrFilterOpaqueResponse(
|
||
OpaqueResponseBlocker* aORB, const nsAString& aReason,
|
||
const OpaqueResponseBlockedTelemetryReason aTelemetryReason,
|
||
const char* aFormat, ...) {
|
||
NimbusFeatures::RecordExposureEvent("opaqueResponseBlocking"_ns, true);
|
||
|
||
const bool shouldFilter =
|
||
ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::BlockedByORB);
|
||
|
||
if (MOZ_UNLIKELY(MOZ_LOG_TEST(GetORBLog(), LogLevel::Debug))) {
|
||
va_list ap;
|
||
va_start(ap, aFormat);
|
||
nsVprintfCString logString(aFormat, ap);
|
||
va_end(ap);
|
||
|
||
LOGORB("%s: %s", shouldFilter ? "Filtered" : "Blocked", logString.get());
|
||
}
|
||
|
||
if (shouldFilter) {
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eFilteredFetch)
|
||
.Add();
|
||
// The existence of `mORB` depends on `BlockOrFilterOpaqueResponse` being
|
||
// called before or after sniffing has completed.
|
||
// Another requirement is that `OpaqueResponseFilter` must come after
|
||
// `OpaqueResponseBlocker`, which is why in the case of having an
|
||
// `OpaqueResponseBlocker` we let it handle creating an
|
||
// `OpaqueResponseFilter`.
|
||
if (aORB) {
|
||
MOZ_DIAGNOSTIC_ASSERT(!mORB || aORB == mORB);
|
||
aORB->FilterResponse();
|
||
} else {
|
||
mListener = new OpaqueResponseFilter(mListener);
|
||
}
|
||
return OpaqueResponse::Allow;
|
||
}
|
||
|
||
LogORBError(aReason, aTelemetryReason);
|
||
return OpaqueResponse::Block;
|
||
}
|
||
|
||
// The specification for ORB is currently being written:
|
||
// https://whatpr.org/fetch/1442.html#orb-algorithm
|
||
// The `opaque-response-safelist check` is implemented in:
|
||
// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff`
|
||
// * `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
|
||
// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff`
|
||
// * `OpaqueResponseBlocker::ValidateJavaScript`
|
||
OpaqueResponse
|
||
HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff() {
|
||
MOZ_ASSERT(XRE_IsParentProcess());
|
||
|
||
// https://whatpr.org/fetch/1442.html#http-fetch, step 6.4
|
||
if (!ShouldBlockOpaqueResponse()) {
|
||
return OpaqueResponse::Allow;
|
||
}
|
||
|
||
// Regardless of if ORB is enabled or not, we check if we should filter the
|
||
// response in the parent. This way data won't reach a content process that
|
||
// will create a filtered `Response` object. This is enabled when
|
||
// 'browser.opaqueResponseBlocking.filterFetchResponse' is
|
||
// `OpaqueResponseFilterFetch::All`.
|
||
// See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
|
||
if (ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::All)) {
|
||
mListener = new OpaqueResponseFilter(mListener);
|
||
|
||
// If we're filtering a response in the parent, there will be no data to
|
||
// determine if it should be blocked or not so the only option we have is to
|
||
// allow it.
|
||
return OpaqueResponse::Allow;
|
||
}
|
||
|
||
if (!mCachedOpaqueResponseBlockingPref) {
|
||
return OpaqueResponse::Allow;
|
||
}
|
||
|
||
// If ORB is enabled, we check if we should filter the response in the parent.
|
||
// This way data won't reach a content process that will create a filtered
|
||
// `Response` object. We allow ORB to determine if the response should be
|
||
// blocked or filtered, but regardless no data should reach the content
|
||
// process. This is enabled when
|
||
// 'browser.opaqueResponseBlocking.filterFetchResponse' is
|
||
// `OpaqueResponseFilterFetch::AllowedByORB`.
|
||
// See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
|
||
if (ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::AllowedByORB)) {
|
||
mListener = new OpaqueResponseFilter(mListener);
|
||
}
|
||
|
||
glean::opaque_response_blocking::cross_origin_opaque_response_count.Add(1);
|
||
|
||
PROFILER_MARKER_TEXT("ORB safelist check", NETWORK, {}, "Before sniff"_ns);
|
||
|
||
// https://whatpr.org/fetch/1442.html#orb-algorithm
|
||
// Step 1
|
||
nsAutoCString contentType;
|
||
mResponseHead->ContentType(contentType);
|
||
|
||
// Step 2
|
||
nsAutoCString contentTypeOptionsHeader;
|
||
bool nosniff =
|
||
mResponseHead->GetContentTypeOptionsHeader(contentTypeOptionsHeader) &&
|
||
contentTypeOptionsHeader.EqualsIgnoreCase("nosniff");
|
||
|
||
// Step 3
|
||
switch (GetOpaqueResponseBlockedReason(contentType, mResponseHead->Status(),
|
||
nosniff)) {
|
||
case OpaqueResponseBlockedReason::ALLOWED_SAFE_LISTED:
|
||
// Step 3.1
|
||
return OpaqueResponse::Allow;
|
||
case OpaqueResponseBlockedReason::ALLOWED_SAFE_LISTED_SPEC_BREAKING:
|
||
LOGORB("Allowed %s in a spec breaking way", contentType.get());
|
||
return OpaqueResponse::Allow;
|
||
case OpaqueResponseBlockedReason::BLOCKED_BLOCKLISTED_NEVER_SNIFFED:
|
||
return BlockOrFilterOpaqueResponse(
|
||
mORB, u"mimeType is an opaque-blocklisted-never-sniffed MIME type"_ns,
|
||
OpaqueResponseBlockedTelemetryReason::eMimeNeverSniffed,
|
||
"BLOCKED_BLOCKLISTED_NEVER_SNIFFED");
|
||
case OpaqueResponseBlockedReason::BLOCKED_206_AND_BLOCKLISTED:
|
||
// Step 3.3
|
||
return BlockOrFilterOpaqueResponse(
|
||
mORB,
|
||
u"response's status is 206 and mimeType is an opaque-blocklisted MIME type"_ns,
|
||
OpaqueResponseBlockedTelemetryReason::eResp206Blclisted,
|
||
"BLOCKED_206_AND_BLOCKEDLISTED");
|
||
case OpaqueResponseBlockedReason::
|
||
BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN:
|
||
// Step 3.4
|
||
return BlockOrFilterOpaqueResponse(
|
||
mORB,
|
||
u"nosniff is true and mimeType is an opaque-blocklisted MIME type or its essence is 'text/plain'"_ns,
|
||
OpaqueResponseBlockedTelemetryReason::eNosniffBlcOrTextp,
|
||
"BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN");
|
||
default:
|
||
break;
|
||
}
|
||
|
||
// Step 4
|
||
// If it's a media subsequent request, we assume that it will only be made
|
||
// after a successful initial request.
|
||
bool isMediaRequest;
|
||
mLoadInfo->GetIsMediaRequest(&isMediaRequest);
|
||
if (isMediaRequest) {
|
||
bool isMediaInitialRequest;
|
||
mLoadInfo->GetIsMediaInitialRequest(&isMediaInitialRequest);
|
||
if (!isMediaInitialRequest) {
|
||
return OpaqueResponse::Allow;
|
||
}
|
||
}
|
||
|
||
// Step 5
|
||
if (mResponseHead->Status() == 206 &&
|
||
!IsFirstPartialResponse(*mResponseHead)) {
|
||
return BlockOrFilterOpaqueResponse(
|
||
mORB, u"response status is 206 and not first partial response"_ns,
|
||
OpaqueResponseBlockedTelemetryReason::eResp206Blclisted,
|
||
"Is not a valid partial response given 0");
|
||
}
|
||
|
||
// Setup for steps 6, 7, 8 and 10.
|
||
// Steps 6 and 7 are handled by the sniffer framework.
|
||
// Steps 8 and 10 by are handled by
|
||
// `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
|
||
if (mLoadFlags & nsIChannel::LOAD_CALL_CONTENT_SNIFFERS) {
|
||
mSnifferCategoryType = SnifferCategoryType::All;
|
||
} else {
|
||
mSnifferCategoryType = SnifferCategoryType::OpaqueResponseBlocking;
|
||
}
|
||
|
||
mLoadFlags |= (nsIChannel::LOAD_CALL_CONTENT_SNIFFERS |
|
||
nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE);
|
||
|
||
// Install an input stream listener that performs ORB checks that depend on
|
||
// inspecting the incoming data. It is crucial that `OnStartRequest` is called
|
||
// on this listener either after sniffing is completed or that we skip
|
||
// sniffing, otherwise `OpaqueResponseBlocker` will allow responses that it
|
||
// shouldn't.
|
||
mORB = new OpaqueResponseBlocker(mListener, this, contentType, nosniff);
|
||
mListener = mORB;
|
||
|
||
nsAutoCString contentEncoding;
|
||
nsresult rv =
|
||
mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
|
||
|
||
if (NS_SUCCEEDED(rv) && !contentEncoding.IsEmpty()) {
|
||
return OpaqueResponse::SniffCompressed;
|
||
}
|
||
mLoadFlags |= (nsIChannel::LOAD_CALL_CONTENT_SNIFFERS |
|
||
nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE);
|
||
return OpaqueResponse::Sniff;
|
||
}
|
||
|
||
// The specification for ORB is currently being written:
|
||
// https://whatpr.org/fetch/1442.html#orb-algorithm
|
||
// The `opaque-response-safelist check` is implemented in:
|
||
// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff`
|
||
// * `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
|
||
// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff`
|
||
// * `OpaqueResponseBlocker::ValidateJavaScript`
|
||
OpaqueResponse HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff(
|
||
const nsACString& aContentType, bool aNoSniff) {
|
||
PROFILER_MARKER_TEXT("ORB safelist check", NETWORK, {}, "After sniff"_ns);
|
||
|
||
// https://whatpr.org/fetch/1442.html#orb-algorithm
|
||
MOZ_ASSERT(XRE_IsParentProcess());
|
||
MOZ_ASSERT(mCachedOpaqueResponseBlockingPref);
|
||
|
||
// Step 9
|
||
bool isMediaRequest;
|
||
mLoadInfo->GetIsMediaRequest(&isMediaRequest);
|
||
if (isMediaRequest) {
|
||
return BlockOrFilterOpaqueResponse(
|
||
mORB, u"after sniff: media request"_ns,
|
||
OpaqueResponseBlockedTelemetryReason::eAfterSniffMedia,
|
||
"media request");
|
||
}
|
||
|
||
// Step 11
|
||
if (aNoSniff) {
|
||
return BlockOrFilterOpaqueResponse(
|
||
mORB, u"after sniff: nosniff is true"_ns,
|
||
OpaqueResponseBlockedTelemetryReason::eAfterSniffNosniff, "nosniff");
|
||
}
|
||
|
||
// Step 12
|
||
if (mResponseHead &&
|
||
(mResponseHead->Status() < 200 || mResponseHead->Status() > 299)) {
|
||
return BlockOrFilterOpaqueResponse(
|
||
mORB, u"after sniff: status code is not in allowed range"_ns,
|
||
OpaqueResponseBlockedTelemetryReason::eAfterSniffStaCode,
|
||
"status code (%d) is not allowed", mResponseHead->Status());
|
||
}
|
||
|
||
// Step 13
|
||
if (!mResponseHead || aContentType.IsEmpty()) {
|
||
LOGORB("Allowed: mimeType is failure");
|
||
return OpaqueResponse::Allow;
|
||
}
|
||
|
||
// Step 14
|
||
if (StringBeginsWith(aContentType, "image/"_ns) ||
|
||
StringBeginsWith(aContentType, "video/"_ns) ||
|
||
StringBeginsWith(aContentType, "audio/"_ns)) {
|
||
return BlockOrFilterOpaqueResponse(
|
||
mORB,
|
||
u"after sniff: content-type declares image/video/audio, but sniffing fails"_ns,
|
||
OpaqueResponseBlockedTelemetryReason::eAfterSniffCtFail,
|
||
"ContentType is image/video/audio");
|
||
}
|
||
|
||
return OpaqueResponse::Sniff;
|
||
}
|
||
|
||
bool HttpBaseChannel::NeedOpaqueResponseAllowedCheckAfterSniff() const {
|
||
return mORB ? mORB->IsSniffing() : false;
|
||
}
|
||
|
||
void HttpBaseChannel::BlockOpaqueResponseAfterSniff(
|
||
const nsAString& aReason,
|
||
const OpaqueResponseBlockedTelemetryReason aTelemetryReason) {
|
||
MOZ_DIAGNOSTIC_ASSERT(mORB);
|
||
LogORBError(aReason, aTelemetryReason);
|
||
mORB->BlockResponse(this, NS_BINDING_ABORTED);
|
||
}
|
||
|
||
void HttpBaseChannel::AllowOpaqueResponseAfterSniff() {
|
||
MOZ_DIAGNOSTIC_ASSERT(mORB);
|
||
mORB->AllowResponse();
|
||
}
|
||
|
||
void HttpBaseChannel::SetChannelBlockedByOpaqueResponse() {
|
||
mChannelBlockedByOpaqueResponse = true;
|
||
|
||
RefPtr<dom::BrowsingContext> browsingContext =
|
||
dom::BrowsingContext::GetCurrentTopByBrowserId(mBrowserId);
|
||
if (!browsingContext) {
|
||
return;
|
||
}
|
||
|
||
dom::WindowContext* windowContext = browsingContext->GetTopWindowContext();
|
||
if (windowContext) {
|
||
windowContext->Canonical()->SetShouldReportHasBlockedOpaqueResponse(
|
||
mLoadInfo->InternalContentPolicyType());
|
||
}
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetCookieHeaders(const nsTArray<nsCString>& aCookieHeaders) {
|
||
if (mLoadFlags & LOAD_ANONYMOUS) return NS_OK;
|
||
|
||
if (IsBrowsingContextDiscarded()) {
|
||
return NS_OK;
|
||
}
|
||
|
||
// empty header isn't an error
|
||
if (aCookieHeaders.IsEmpty()) {
|
||
return NS_OK;
|
||
}
|
||
|
||
nsICookieService* cs = gHttpHandler->GetCookieService();
|
||
NS_ENSURE_TRUE(cs, NS_ERROR_FAILURE);
|
||
|
||
for (const nsCString& cookieHeader : aCookieHeaders) {
|
||
nsresult rv = cs->SetCookieStringFromHttp(mURI, cookieHeader, this);
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
}
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetThirdPartyFlags(uint32_t* aFlags) {
|
||
*aFlags = LoadThirdPartyFlags();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetThirdPartyFlags(uint32_t aFlags) {
|
||
ENSURE_CALLED_BEFORE_ASYNC_OPEN();
|
||
|
||
StoreThirdPartyFlags(aFlags);
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetForceAllowThirdPartyCookie(bool* aForce) {
|
||
*aForce = !!(LoadThirdPartyFlags() &
|
||
nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetForceAllowThirdPartyCookie(bool aForce) {
|
||
ENSURE_CALLED_BEFORE_ASYNC_OPEN();
|
||
|
||
if (aForce) {
|
||
StoreThirdPartyFlags(LoadThirdPartyFlags() |
|
||
nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
|
||
} else {
|
||
StoreThirdPartyFlags(LoadThirdPartyFlags() &
|
||
~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 = LoadChannelIsForDownload();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetChannelIsForDownload(bool aChannelIsForDownload) {
|
||
StoreChannelIsForDownload(aChannelIsForDownload);
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetCacheKeysRedirectChain(nsTArray<nsCString>* cacheKeys) {
|
||
auto RedirectedCachekeys = mRedirectedCachekeys.Lock();
|
||
auto& ref = RedirectedCachekeys.ref();
|
||
ref = 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);
|
||
mSelfAddr.ToStringBuffer(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 (const 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, 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);
|
||
mPeerAddr.ToStringBuffer(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(bool aTlsTunnel) {
|
||
ENSURE_CALLED_BEFORE_CONNECT();
|
||
|
||
if (!mUpgradeProtocolCallback) {
|
||
return NS_ERROR_FAILURE;
|
||
}
|
||
|
||
mCaps |= NS_HTTP_CONNECT_ONLY;
|
||
if (aTlsTunnel) {
|
||
mCaps |= NS_HTTP_TLS_TUNNEL;
|
||
}
|
||
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 = LoadAllowSpdy();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetAllowSpdy(bool aAllowSpdy) {
|
||
StoreAllowSpdy(aAllowSpdy);
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetAllowHttp3(bool* aAllowHttp3) {
|
||
NS_ENSURE_ARG_POINTER(aAllowHttp3);
|
||
|
||
*aAllowHttp3 = LoadAllowHttp3();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetAllowHttp3(bool aAllowHttp3) {
|
||
StoreAllowHttp3(aAllowHttp3);
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetAllowAltSvc(bool* aAllowAltSvc) {
|
||
NS_ENSURE_ARG_POINTER(aAllowAltSvc);
|
||
|
||
*aAllowAltSvc = LoadAllowAltSvc();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetAllowAltSvc(bool aAllowAltSvc) {
|
||
StoreAllowAltSvc(aAllowAltSvc);
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetBeConservative(bool* aBeConservative) {
|
||
NS_ENSURE_ARG_POINTER(aBeConservative);
|
||
|
||
*aBeConservative = LoadBeConservative();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetBeConservative(bool aBeConservative) {
|
||
StoreBeConservative(aBeConservative);
|
||
return NS_OK;
|
||
}
|
||
|
||
bool HttpBaseChannel::BypassProxy() {
|
||
return StaticPrefs::network_proxy_allow_bypass() && LoadBypassProxy();
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetBypassProxy(bool* aBypassProxy) {
|
||
NS_ENSURE_ARG_POINTER(aBypassProxy);
|
||
|
||
*aBypassProxy = BypassProxy();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetBypassProxy(bool aBypassProxy) {
|
||
if (StaticPrefs::network_proxy_allow_bypass()) {
|
||
StoreBypassProxy(aBypassProxy);
|
||
} else {
|
||
NS_WARNING("bypassProxy set but network.proxy.allow_bypass is disabled");
|
||
return NS_ERROR_FAILURE;
|
||
}
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetIsTRRServiceChannel(bool* aIsTRRServiceChannel) {
|
||
NS_ENSURE_ARG_POINTER(aIsTRRServiceChannel);
|
||
|
||
*aIsTRRServiceChannel = LoadIsTRRServiceChannel();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetIsTRRServiceChannel(bool aIsTRRServiceChannel) {
|
||
StoreIsTRRServiceChannel(aIsTRRServiceChannel);
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetIsResolvedByTRR(bool* aResolvedByTRR) {
|
||
NS_ENSURE_ARG_POINTER(aResolvedByTRR);
|
||
*aResolvedByTRR = LoadResolvedByTRR();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetEffectiveTRRMode(nsIRequest::TRRMode* aEffectiveTRRMode) {
|
||
*aEffectiveTRRMode = mEffectiveTRRMode;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetTrrSkipReason(nsITRRSkipReason::value* aTrrSkipReason) {
|
||
*aTrrSkipReason = mTRRSkipReason;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetIsLoadedBySocketProcess(bool* aResult) {
|
||
NS_ENSURE_ARG_POINTER(aResult);
|
||
*aResult = LoadLoadedBySocketProcess();
|
||
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) {
|
||
if (!mAPIRedirectTo) {
|
||
return NS_ERROR_NOT_AVAILABLE;
|
||
}
|
||
NS_ENSURE_ARG_POINTER(aResult);
|
||
*aResult = do_AddRef(mAPIRedirectTo->first()).take();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetResponseTimeoutEnabled(bool* aEnable) {
|
||
if (NS_WARN_IF(!aEnable)) {
|
||
return NS_ERROR_NULL_POINTER;
|
||
}
|
||
*aEnable = LoadResponseTimeoutEnabled();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetResponseTimeoutEnabled(bool aEnable) {
|
||
StoreResponseTimeoutEnabled(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) {
|
||
StoreForcePending(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 = LoadCorsIncludeCredentials();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetCorsIncludeCredentials(bool aInclude) {
|
||
StoreCorsIncludeCredentials(aInclude);
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetRequestMode(RequestMode* aMode) {
|
||
*aMode = mRequestMode;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetRequestMode(RequestMode aMode) {
|
||
mRequestMode = 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) ||
|
||
LoadForceValidateCacheContent()) {
|
||
*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();
|
||
}
|
||
|
||
bool HttpBaseChannel::IsNavigation() {
|
||
return LoadForceMainDocumentChannel() || (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;
|
||
|
||
if (!StaticPrefs::dom_serviceWorkers_enabled()) {
|
||
return 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)LoadAddedAsNonTailRequest()));
|
||
|
||
if (!LoadAddedAsNonTailRequest()) {
|
||
mRequestContext->AddNonTailRequest();
|
||
StoreAddedAsNonTailRequest(true);
|
||
}
|
||
}
|
||
}
|
||
|
||
void HttpBaseChannel::RemoveAsNonTailRequest() {
|
||
MOZ_ASSERT(NS_IsMainThread());
|
||
|
||
if (mRequestContext) {
|
||
LOG(
|
||
("HttpBaseChannel::RemoveAsNonTailRequest this=%p, rc=%p, already "
|
||
"added=%d",
|
||
this, mRequestContext.get(), (bool)LoadAddedAsNonTailRequest()));
|
||
|
||
if (LoadAddedAsNonTailRequest()) {
|
||
mRequestContext->RemoveNonTailRequest();
|
||
StoreAddedAsNonTailRequest(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();
|
||
|
||
ExtContentPolicyType contentPolicyType =
|
||
mLoadInfo->GetExternalContentPolicyType();
|
||
if (contentPolicyType == ExtContentPolicy::TYPE_DOCUMENT ||
|
||
contentPolicyType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
|
||
// Reset PrincipalToInherit to a null principal. We'll credit the the
|
||
// redirecting resource's result principal as the new principal's precursor.
|
||
// This means that a data: URI will end up loading in a process based on the
|
||
// redirected-from URI.
|
||
nsCOMPtr<nsIPrincipal> redirectPrincipal;
|
||
nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
|
||
this, getter_AddRefs(redirectPrincipal));
|
||
nsCOMPtr<nsIPrincipal> nullPrincipalToInherit =
|
||
NullPrincipal::CreateWithInheritedAttributes(redirectPrincipal);
|
||
newLoadInfo->SetPrincipalToInherit(nullPrincipalToInherit);
|
||
}
|
||
|
||
bool isTopLevelDoc = newLoadInfo->GetExternalContentPolicyType() ==
|
||
ExtContentPolicy::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));
|
||
|
||
// Reset our sandboxed null principal ID when cloning loadInfo for an
|
||
// externally visible redirect.
|
||
if (!isInternalRedirect) {
|
||
// If we've redirected from http to something that isn't, clear
|
||
// the "external" flag, as loads that now go to other apps should be
|
||
// allowed to go ahead and not trip infinite-loop protection
|
||
// (see bug 1717314 for context).
|
||
if (!net::SchemeIsHttpOrHttps(aNewURI)) {
|
||
newLoadInfo->SetLoadTriggeredFromExternal(false);
|
||
}
|
||
newLoadInfo->ResetSandboxedNullPrincipalID();
|
||
|
||
if (isTopLevelDoc) {
|
||
// Reset HTTPS-first and -only status on http redirect. To not
|
||
// unexpectedly downgrade requests that weren't upgraded via HTTPS-First
|
||
// (Bug 1904238).
|
||
Unused << newLoadInfo->SetHttpsOnlyStatus(
|
||
nsILoadInfo::HTTPS_ONLY_UNINITIALIZED);
|
||
|
||
// Reset schemeless status flag to prevent schemeless HTTPS-First from
|
||
// repeatedly trying to upgrade loads that get downgraded again from the
|
||
// server by a redirect (Bug 1937386).
|
||
Unused << newLoadInfo->SetSchemelessInput(
|
||
nsILoadInfo::SchemelessInputTypeUnset);
|
||
}
|
||
}
|
||
|
||
newLoadInfo->AppendRedirectHistoryEntry(this, isInternalRedirect);
|
||
|
||
return newLoadInfo.forget();
|
||
}
|
||
|
||
//-----------------------------------------------------------------------------
|
||
// nsHttpChannel::nsITraceableChannel
|
||
//-----------------------------------------------------------------------------
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetNewListener(nsIStreamListener* aListener,
|
||
bool aMustApplyContentConversion,
|
||
nsIStreamListener** _retval) {
|
||
LOG((
|
||
"HttpBaseChannel::SetNewListener [this=%p, mListener=%p, newListener=%p]",
|
||
this, mListener.get(), aListener));
|
||
|
||
if (!LoadTracingEnabled()) return NS_ERROR_FAILURE;
|
||
|
||
NS_ENSURE_STATE(mListener);
|
||
NS_ENSURE_ARG_POINTER(aListener);
|
||
|
||
nsCOMPtr<nsIStreamListener> wrapper = new nsStreamListenerWrapper(mListener);
|
||
|
||
wrapper.forget(_retval);
|
||
mListener = aListener;
|
||
if (aMustApplyContentConversion) {
|
||
StoreListenerRequiresContentConversion(true);
|
||
}
|
||
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;
|
||
mORB = 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 AfterOnStartRequestBegun to true
|
||
// before notifying listener.
|
||
if (!LoadAfterOnStartRequestBegun()) {
|
||
StoreAfterOnStartRequestBegun(true);
|
||
}
|
||
|
||
if (mListener && !LoadOnStartRequestCalled()) {
|
||
nsCOMPtr<nsIStreamListener> listener = mListener;
|
||
StoreOnStartRequestCalled(true);
|
||
listener->OnStartRequest(this);
|
||
}
|
||
StoreOnStartRequestCalled(true);
|
||
|
||
// Make sure IsPending 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.
|
||
StoreIsPending(false);
|
||
|
||
// notify "http-on-before-stop-request" observers
|
||
gHttpHandler->OnBeforeStopRequest(this);
|
||
|
||
if (mListener && !LoadOnStopRequestCalled()) {
|
||
nsCOMPtr<nsIStreamListener> listener = mListener;
|
||
StoreOnStopRequestCalled(true);
|
||
listener->OnStopRequest(this, mStatus);
|
||
}
|
||
StoreOnStopRequestCalled(true);
|
||
|
||
// notify "http-on-stop-request" 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(nsHttp::Cookie.val(), 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);
|
||
}
|
||
|
||
// In case we are here because an upgrade happened through mixed content
|
||
// upgrading, CSP upgrade-insecure-requests, HTTPS-Only or HTTPS-First, we
|
||
// have to recalculate the referrer based on the original referrer to
|
||
// account for the different scheme. This does NOT apply to HSTS.
|
||
// See Bug 1857894 and order of https://fetch.spec.whatwg.org/#main-fetch.
|
||
// Otherwise, if we have a new referrer policy, we want to recalculate the
|
||
// referrer based on the old computed referrer (Bug 1678545).
|
||
bool wasNonHSTSUpgrade =
|
||
(aRedirectFlags & nsIChannelEventSink::REDIRECT_STS_UPGRADE) &&
|
||
(!mLoadInfo->GetHstsStatus());
|
||
if (wasNonHSTSUpgrade) {
|
||
nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetOriginalReferrer();
|
||
config.referrerInfo =
|
||
new dom::ReferrerInfo(referrer, mReferrerInfo->ReferrerPolicy(),
|
||
mReferrerInfo->GetSendReferrer());
|
||
} else if (referrerPolicy != dom::ReferrerPolicy::_empty) {
|
||
nsCOMPtr<nsIURI> referrer = mReferrerInfo->GetComputedReferrer();
|
||
config.referrerInfo = new dom::ReferrerInfo(
|
||
referrer, referrerPolicy, mReferrerInfo->GetSendReferrer());
|
||
} else {
|
||
config.referrerInfo = mReferrerInfo;
|
||
}
|
||
}
|
||
}
|
||
|
||
nsCOMPtr<nsITimedChannel> oldTimedChannel(
|
||
do_QueryInterface(static_cast<nsIHttpChannel*>(this)));
|
||
if (oldTimedChannel) {
|
||
config.timedChannelInfo = Some(dom::TimedChannelInfo());
|
||
config.timedChannelInfo->redirectCount() = mRedirectCount;
|
||
config.timedChannelInfo->internalRedirectCount() = mInternalRedirectCount;
|
||
config.timedChannelInfo->asyncOpen() = mAsyncOpenTime;
|
||
config.timedChannelInfo->channelCreation() = mChannelCreationTimestamp;
|
||
config.timedChannelInfo->redirectStart() = mRedirectStartTimeStamp;
|
||
config.timedChannelInfo->redirectEnd() = mRedirectEndTimeStamp;
|
||
config.timedChannelInfo->initiatorType() = mInitiatorType;
|
||
config.timedChannelInfo->allRedirectsSameOrigin() =
|
||
LoadAllRedirectsSameOrigin();
|
||
config.timedChannelInfo->allRedirectsPassTimingAllowCheck() =
|
||
LoadAllRedirectsPassTimingAllowCheck();
|
||
// 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() !=
|
||
ExtContentPolicy::TYPE_DOCUMENT) {
|
||
nsCOMPtr<nsIPrincipal> principal = loadInfo->GetLoadingPrincipal();
|
||
config.timedChannelInfo->timingAllowCheckForPrincipal() =
|
||
Some(oldTimedChannel->TimingAllowCheck(principal));
|
||
}
|
||
|
||
config.timedChannelInfo->allRedirectsPassTimingAllowCheck() =
|
||
LoadAllRedirectsPassTimingAllowCheck();
|
||
config.timedChannelInfo->launchServiceWorkerStart() =
|
||
mLaunchServiceWorkerStart;
|
||
config.timedChannelInfo->launchServiceWorkerEnd() = mLaunchServiceWorkerEnd;
|
||
config.timedChannelInfo->dispatchFetchEventStart() =
|
||
mDispatchFetchEventStart;
|
||
config.timedChannelInfo->dispatchFetchEventEnd() = mDispatchFetchEventEnd;
|
||
config.timedChannelInfo->handleFetchEventStart() = mHandleFetchEventStart;
|
||
config.timedChannelInfo->handleFetchEventEnd() = mHandleFetchEventEnd;
|
||
config.timedChannelInfo->responseStart() =
|
||
mTransactionTimings.responseStart;
|
||
config.timedChannelInfo->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 = LoadUploadStreamHasHeaders();
|
||
|
||
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->SetClassOfService(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.timedChannelInfo && newTimedChannel) {
|
||
// 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.timedChannelInfo->redirectCount());
|
||
int32_t newCount = config.timedChannelInfo->internalRedirectCount() + 1;
|
||
newTimedChannel->SetInternalRedirectCount(std::max(
|
||
newCount, static_cast<int32_t>(
|
||
config.timedChannelInfo->internalRedirectCount())));
|
||
} else {
|
||
int32_t newCount = config.timedChannelInfo->redirectCount() + 1;
|
||
newTimedChannel->SetRedirectCount(std::max(
|
||
newCount,
|
||
static_cast<int32_t>(config.timedChannelInfo->redirectCount())));
|
||
newTimedChannel->SetInternalRedirectCount(
|
||
config.timedChannelInfo->internalRedirectCount());
|
||
}
|
||
|
||
if (shouldHideTiming) {
|
||
if (!config.timedChannelInfo->channelCreation().IsNull()) {
|
||
newTimedChannel->SetChannelCreation(
|
||
config.timedChannelInfo->channelCreation());
|
||
}
|
||
|
||
if (!config.timedChannelInfo->asyncOpen().IsNull()) {
|
||
newTimedChannel->SetAsyncOpen(config.timedChannelInfo->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.timedChannelInfo->redirectStart().IsNull()) {
|
||
// Only do this for real redirects. Internal redirects should be hidden.
|
||
if (!shouldHideTiming) {
|
||
newTimedChannel->SetRedirectStart(config.timedChannelInfo->asyncOpen());
|
||
}
|
||
} else {
|
||
newTimedChannel->SetRedirectStart(
|
||
config.timedChannelInfo->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.timedChannelInfo->redirectEnd();
|
||
} else if (!config.timedChannelInfo->responseEnd().IsNull()) {
|
||
newRedirectEnd = config.timedChannelInfo->responseEnd();
|
||
} else {
|
||
newRedirectEnd = TimeStamp::Now();
|
||
}
|
||
newTimedChannel->SetRedirectEnd(newRedirectEnd);
|
||
|
||
newTimedChannel->SetInitiatorType(config.timedChannelInfo->initiatorType());
|
||
|
||
nsCOMPtr<nsILoadInfo> loadInfo = newChannel->LoadInfo();
|
||
MOZ_ASSERT(loadInfo);
|
||
|
||
newTimedChannel->SetAllRedirectsSameOrigin(
|
||
config.timedChannelInfo->allRedirectsSameOrigin());
|
||
|
||
if (config.timedChannelInfo->timingAllowCheckForPrincipal()) {
|
||
newTimedChannel->SetAllRedirectsPassTimingAllowCheck(
|
||
config.timedChannelInfo->allRedirectsPassTimingAllowCheck() &&
|
||
*config.timedChannelInfo->timingAllowCheckForPrincipal());
|
||
}
|
||
|
||
// Propagate service worker measurements across redirects. The
|
||
// PeformanceResourceTiming.workerStart API expects to see the
|
||
// worker start time after a redirect.
|
||
newTimedChannel->SetLaunchServiceWorkerStart(
|
||
config.timedChannelInfo->launchServiceWorkerStart());
|
||
newTimedChannel->SetLaunchServiceWorkerEnd(
|
||
config.timedChannelInfo->launchServiceWorkerEnd());
|
||
newTimedChannel->SetDispatchFetchEventStart(
|
||
config.timedChannelInfo->dispatchFetchEventStart());
|
||
newTimedChannel->SetDispatchFetchEventEnd(
|
||
config.timedChannelInfo->dispatchFetchEventEnd());
|
||
newTimedChannel->SetHandleFetchEventStart(
|
||
config.timedChannelInfo->handleFetchEventStart());
|
||
newTimedChannel->SetHandleFetchEventEnd(
|
||
config.timedChannelInfo->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, ""_ns,
|
||
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();
|
||
timedChannelInfo = aInit.timedChannelInfo();
|
||
uploadStream = aInit.uploadStream();
|
||
uploadStreamLength = aInit.uploadStreamLength();
|
||
uploadStreamHasHeaders = aInit.uploadStreamHasHeaders();
|
||
contentType = aInit.contentType();
|
||
contentLength = aInit.contentLength();
|
||
}
|
||
|
||
dom::ReplacementChannelConfigInit
|
||
HttpBaseChannel::ReplacementChannelConfig::Serialize() {
|
||
dom::ReplacementChannelConfigInit config;
|
||
config.redirectFlags() = redirectFlags;
|
||
config.classOfService() = classOfService;
|
||
config.privateBrowsing() = privateBrowsing;
|
||
config.method() = method;
|
||
config.referrerInfo() = referrerInfo;
|
||
config.timedChannelInfo() = timedChannelInfo;
|
||
config.uploadStream() =
|
||
uploadStream ? RemoteLazyInputStream::WrapStream(uploadStream) : nullptr;
|
||
config.uploadStreamLength() = uploadStreamLength;
|
||
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;
|
||
}
|
||
|
||
newChannel->SetLoadFlags(loadFlags);
|
||
|
||
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
|
||
|
||
ReplacementReason redirectType =
|
||
redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
|
||
nsIChannelEventSink::REDIRECT_TRANSPARENT)
|
||
? 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));
|
||
bool sameOriginWithOriginalUri = SameOriginWithOriginalUri(newURI);
|
||
if (config.timedChannelInfo && newTimedChannel) {
|
||
newTimedChannel->SetAllRedirectsSameOrigin(
|
||
config.timedChannelInfo->allRedirectsSameOrigin() &&
|
||
sameOriginWithOriginalUri);
|
||
}
|
||
|
||
newChannel->SetLoadGroup(mLoadGroup);
|
||
newChannel->SetNotificationCallbacks(mCallbacks);
|
||
// TODO: create tests for cross-origin redirect in bug 1662896.
|
||
if (sameOriginWithOriginalUri) {
|
||
newChannel->SetContentDisposition(mContentDispositionHint);
|
||
if (mContentDispositionFilename) {
|
||
newChannel->SetContentDispositionFilename(*mContentDispositionFilename);
|
||
}
|
||
}
|
||
|
||
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 (LoadRequireCORSPreflight()) {
|
||
httpInternal->SetCorsPreflightParameters(mUnsafeHeaders, false, false);
|
||
}
|
||
}
|
||
|
||
// convey the LoadAllowSTS() flags
|
||
rv = httpChannel->SetAllowSTS(LoadAllowSTS());
|
||
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 && mRequestMode == RequestMode::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));
|
||
}
|
||
}
|
||
|
||
// convery the IsUserAgentHeaderModified value.
|
||
if (httpInternal) {
|
||
rv = httpInternal->SetIsUserAgentHeaderModified(
|
||
LoadIsUserAgentHeaderModified());
|
||
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->SetBrowserId(mBrowserId);
|
||
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(LoadForceMainDocumentChannel());
|
||
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(LoadThirdPartyFlags());
|
||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||
rv = httpInternal->SetAllowSpdy(LoadAllowSpdy());
|
||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||
rv = httpInternal->SetAllowHttp3(LoadAllowHttp3());
|
||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||
rv = httpInternal->SetAllowAltSvc(LoadAllowAltSvc());
|
||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||
rv = httpInternal->SetBeConservative(LoadBeConservative());
|
||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||
rv = httpInternal->SetIsTRRServiceChannel(LoadIsTRRServiceChannel());
|
||
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);
|
||
|
||
realChannel->StoreTaintedOriginFlag(
|
||
ShouldTaintReplacementChannelOrigin(newChannel, redirectFlags));
|
||
}
|
||
|
||
// 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)
|
||
{
|
||
auto redirectedCachekeys = mRedirectedCachekeys.Lock();
|
||
auto& ref = redirectedCachekeys.ref();
|
||
if (ref) {
|
||
LOG(
|
||
("HttpBaseChannel::SetupReplacementChannel "
|
||
"[this=%p] transferring chain of redirect cache-keys",
|
||
this));
|
||
rv = httpInternal->SetCacheKeysRedirectChain(ref.release());
|
||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||
}
|
||
}
|
||
|
||
// Preserve Request mode.
|
||
rv = httpInternal->SetRequestMode(mRequestMode);
|
||
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(LoadAltDataForChild());
|
||
if (LoadDisableAltDataCache()) {
|
||
httpInternal->DisableAltDataCache();
|
||
}
|
||
}
|
||
|
||
// transfer any properties
|
||
nsCOMPtr<nsIWritablePropertyBag> bag(do_QueryInterface(newChannel));
|
||
if (bag) {
|
||
for (const auto& entry : mPropertyHash) {
|
||
bag->SetProperty(entry.GetKey(), entry.GetWeak());
|
||
}
|
||
}
|
||
|
||
// 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 (LoadForceValidateCacheContent()) {
|
||
Unused << cacheInfoChan->SetForceValidateCacheContent(true);
|
||
}
|
||
}
|
||
|
||
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));
|
||
}
|
||
|
||
// we need to strip Authentication headers for cross-origin requests
|
||
// Ref: https://fetch.spec.whatwg.org/#http-redirect-fetch
|
||
nsAutoCString authHeader;
|
||
if (NS_SUCCEEDED(
|
||
httpChannel->GetRequestHeader("Authorization"_ns, authHeader)) &&
|
||
NS_ShouldRemoveAuthHeaderOnRedirect(static_cast<nsIChannel*>(this),
|
||
newChannel, redirectFlags)) {
|
||
rv = httpChannel->SetRequestHeader("Authorization"_ns, ""_ns, false);
|
||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||
}
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
// check whether the new channel is of same origin as the current channel
|
||
bool HttpBaseChannel::IsNewChannelSameOrigin(nsIChannel* aNewChannel) {
|
||
bool isSameOrigin = false;
|
||
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
||
|
||
if (!ssm) {
|
||
return false;
|
||
}
|
||
|
||
nsCOMPtr<nsIURI> newURI;
|
||
NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
|
||
|
||
nsresult rv = ssm->CheckSameOriginURI(newURI, mURI, false, false);
|
||
if (NS_SUCCEEDED(rv)) {
|
||
isSameOrigin = true;
|
||
}
|
||
|
||
return isSameOrigin;
|
||
}
|
||
|
||
bool HttpBaseChannel::ShouldTaintReplacementChannelOrigin(
|
||
nsIChannel* aNewChannel, uint32_t aRedirectFlags) {
|
||
if (LoadTaintedOriginFlag()) {
|
||
return true;
|
||
}
|
||
|
||
if (NS_IsInternalSameURIRedirect(this, aNewChannel, aRedirectFlags) ||
|
||
NS_IsHSTSUpgradeRedirect(this, aNewChannel, aRedirectFlags)) {
|
||
return false;
|
||
}
|
||
|
||
// If new channel is not of same origin we need to taint unless
|
||
// mURI <-> mOriginalURI/LoadingPrincipal are same origin.
|
||
if (IsNewChannelSameOrigin(aNewChannel)) {
|
||
return false;
|
||
}
|
||
|
||
nsresult rv;
|
||
|
||
if (mLoadInfo->GetLoadingPrincipal()) {
|
||
bool sameOrigin = false;
|
||
rv = mLoadInfo->GetLoadingPrincipal()->IsSameOrigin(mURI, &sameOrigin);
|
||
if (NS_FAILED(rv)) {
|
||
return true;
|
||
}
|
||
return !sameOrigin;
|
||
}
|
||
if (!mOriginalURI) {
|
||
return true;
|
||
}
|
||
|
||
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
||
if (!ssm) {
|
||
return true;
|
||
}
|
||
|
||
rv = ssm->CheckSameOriginURI(mOriginalURI, mURI, false, false);
|
||
return NS_FAILED(rv);
|
||
}
|
||
|
||
// Redirect Tracking
|
||
bool HttpBaseChannel::SameOriginWithOriginalUri(nsIURI* aURI) {
|
||
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
||
bool isPrivateWin = mLoadInfo->GetOriginAttributes().IsPrivateBrowsing();
|
||
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::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;
|
||
StoreAsyncOpenTimeOverriden(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 = LoadAllRedirectsSameOrigin();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetAllRedirectsSameOrigin(bool aAllRedirectsSameOrigin) {
|
||
StoreAllRedirectsSameOrigin(aAllRedirectsSameOrigin);
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetAllRedirectsPassTimingAllowCheck(bool* aPassesCheck) {
|
||
*aPassesCheck = LoadAllRedirectsPassTimingAllowCheck();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetAllRedirectsPassTimingAllowCheck(bool aPassesCheck) {
|
||
StoreAllRedirectsPassTimingAllowCheck(aPassesCheck);
|
||
return NS_OK;
|
||
}
|
||
|
||
// https://fetch.spec.whatwg.org/#cors-check
|
||
bool HttpBaseChannel::PerformCORSCheck() {
|
||
// Step 1
|
||
// Let origin be the result of getting `Access-Control-Allow-Origin`
|
||
// from response’s header list.
|
||
nsAutoCString origin;
|
||
nsresult rv = GetResponseHeader("Access-Control-Allow-Origin"_ns, origin);
|
||
|
||
// Step 2
|
||
// If origin is null, then return failure. (Note: null, not 'null').
|
||
if (NS_FAILED(rv) || origin.IsVoid()) {
|
||
return false;
|
||
}
|
||
|
||
// Step 3
|
||
// If request’s credentials mode is not "include"
|
||
// and origin is `*`, then return success.
|
||
uint32_t cookiePolicy = mLoadInfo->GetCookiePolicy();
|
||
if (cookiePolicy != nsILoadInfo::SEC_COOKIES_INCLUDE &&
|
||
origin.EqualsLiteral("*")) {
|
||
return true;
|
||
}
|
||
|
||
// Step 4
|
||
// If the result of byte-serializing a request origin
|
||
// with request is not origin, then return failure.
|
||
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
||
nsCOMPtr<nsIPrincipal> resourcePrincipal;
|
||
rv = ssm->GetChannelURIPrincipal(this, getter_AddRefs(resourcePrincipal));
|
||
if (NS_FAILED(rv) || !resourcePrincipal) {
|
||
return false;
|
||
}
|
||
nsAutoCString serializedOrigin;
|
||
nsContentSecurityManager::GetSerializedOrigin(
|
||
mLoadInfo->TriggeringPrincipal(), resourcePrincipal, serializedOrigin,
|
||
mLoadInfo);
|
||
if (!serializedOrigin.Equals(origin)) {
|
||
return false;
|
||
}
|
||
|
||
// Step 5
|
||
// If request’s credentials mode is not "include", then return success.
|
||
if (cookiePolicy != nsILoadInfo::SEC_COOKIES_INCLUDE) {
|
||
return true;
|
||
}
|
||
|
||
// Step 6
|
||
// Let credentials be the result of getting
|
||
// `Access-Control-Allow-Credentials` from response’s header list.
|
||
nsAutoCString credentials;
|
||
rv = GetResponseHeader("Access-Control-Allow-Credentials"_ns, credentials);
|
||
|
||
// Step 7 and 8
|
||
// If credentials is `true`, then return success.
|
||
// (else) return failure.
|
||
return NS_SUCCEEDED(rv) && credentials.EqualsLiteral("true");
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::BodyInfoAccessAllowedCheck(nsIPrincipal* aOrigin,
|
||
BodyInfoAccess* _retval) {
|
||
// Per the Fetch spec, https://fetch.spec.whatwg.org/#response-body-info,
|
||
// the bodyInfo for Resource Timing and Navigation Timing info consists of
|
||
// encoded size, decoded size, and content type. It is however made opaque
|
||
// whenever the response is turned into a network error, which sets its
|
||
// bodyInfo to its default values (sizes=0, content-type="").
|
||
|
||
// Case 1:
|
||
// "no-cors" -> Upon success, fetch will return an opaque filtered response.
|
||
// An opaque(-redirect) filtered response is a filtered response
|
||
// whose ... body info is a new response body info.
|
||
auto tainting = mLoadInfo->GetTainting();
|
||
if (tainting == mozilla::LoadTainting::Opaque) {
|
||
*_retval = BodyInfoAccess::DISALLOWED;
|
||
return NS_OK;
|
||
}
|
||
|
||
// Case 2:
|
||
// If request’s response tainting is "cors" and a CORS check for request
|
||
// and response returns failure, then return a network error.
|
||
if (tainting == mozilla::LoadTainting::CORS && !PerformCORSCheck()) {
|
||
*_retval = BodyInfoAccess::DISALLOWED;
|
||
return NS_OK;
|
||
}
|
||
|
||
// Otherwise:
|
||
// The fetch response handover, given a fetch params fetchParams
|
||
// and a response response, run these steps:
|
||
// processResponseEndOfBody:
|
||
// - If fetchParams’s request’s mode is not "navigate" or response’s
|
||
// has-cross-origin-redirects is false:
|
||
// - Let mimeType be the result of extracting a MIME type from
|
||
// response’s header list.
|
||
// - If mimeType is not failure, then set bodyInfo’s content type to the
|
||
// result of minimizing a supported MIME type given mimeType.
|
||
dom::RequestMode requestMode;
|
||
MOZ_ALWAYS_SUCCEEDS(GetRequestMode(&requestMode));
|
||
if (requestMode != RequestMode::Navigate || LoadAllRedirectsSameOrigin()) {
|
||
*_retval = BodyInfoAccess::ALLOW_ALL;
|
||
return NS_OK;
|
||
}
|
||
|
||
*_retval = BodyInfoAccess::ALLOW_SIZES;
|
||
return NS_OK;
|
||
}
|
||
|
||
// https://fetch.spec.whatwg.org/#tao-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);
|
||
|
||
nsAutoCString serializedOrigin;
|
||
nsContentSecurityManager::GetSerializedOrigin(aOrigin, resourcePrincipal,
|
||
serializedOrigin, mLoadInfo);
|
||
|
||
// All redirects are same origin
|
||
if (sameOrigin && (!serializedOrigin.IsEmpty() &&
|
||
!serializedOrigin.EqualsLiteral("null"))) {
|
||
*_retval = true;
|
||
return NS_OK;
|
||
}
|
||
|
||
nsAutoCString headerValue;
|
||
rv = GetResponseHeader("Timing-Allow-Origin"_ns, headerValue);
|
||
if (NS_FAILED(rv)) {
|
||
*_retval = false;
|
||
return NS_OK;
|
||
}
|
||
|
||
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 == serializedOrigin || 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::GetTransactionPending(TimeStamp* _retval) {
|
||
*_retval = mTransactionTimings.transactionPending;
|
||
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)
|
||
IMPL_TIMING_ATTR(TransactionPending)
|
||
|
||
#undef IMPL_TIMING_ATTR
|
||
|
||
void HttpBaseChannel::MaybeReportTimingData() {
|
||
// 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;
|
||
}
|
||
|
||
// Devtools can create fetch requests on behalf the content document.
|
||
// If we don't exclude these requests, they'd also be reported
|
||
// to the content document.
|
||
bool isInDevToolsContext;
|
||
mLoadInfo->GetIsInDevToolsContext(&isInDevToolsContext);
|
||
if (isInDevToolsContext) {
|
||
return;
|
||
}
|
||
|
||
mozilla::dom::PerformanceStorage* documentPerformance =
|
||
mLoadInfo->GetPerformanceStorage();
|
||
if (documentPerformance) {
|
||
documentPerformance->AddEntry(this, this);
|
||
return;
|
||
}
|
||
|
||
if (!nsGlobalWindowInner::GetInnerWindowWithId(
|
||
mLoadInfo->GetInnerWindowID())) {
|
||
// The inner window is in a different process.
|
||
dom::ContentChild* child = dom::ContentChild::GetSingleton();
|
||
|
||
if (!child) {
|
||
return;
|
||
}
|
||
nsAutoString initiatorType;
|
||
nsAutoString entryName;
|
||
|
||
UniquePtr<dom::PerformanceTimingData> performanceTimingData(
|
||
dom::PerformanceTimingData::Create(this, this, 0, initiatorType,
|
||
entryName));
|
||
if (!performanceTimingData) {
|
||
return;
|
||
}
|
||
|
||
LoadInfoArgs loadInfoArgs;
|
||
mozilla::ipc::LoadInfoToLoadInfoArgs(mLoadInfo, &loadInfoArgs);
|
||
child->SendReportFrameTimingData(loadInfoArgs, entryName, initiatorType,
|
||
std::move(performanceTimingData));
|
||
}
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetReportResourceTiming(bool enabled) {
|
||
StoreReportTiming(enabled);
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetReportResourceTiming(bool* _retval) {
|
||
*_retval = LoadReportTiming();
|
||
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));
|
||
return static_cast<bool>(mRequestContext);
|
||
}
|
||
|
||
void HttpBaseChannel::EnsureBrowserId() {
|
||
if (mBrowserId) {
|
||
return;
|
||
}
|
||
|
||
RefPtr<dom::BrowsingContext> bc;
|
||
MOZ_ALWAYS_SUCCEEDS(mLoadInfo->GetBrowsingContext(getter_AddRefs(bc)));
|
||
|
||
if (bc) {
|
||
mBrowserId = bc->GetBrowserId();
|
||
}
|
||
}
|
||
|
||
void HttpBaseChannel::SetCorsPreflightParameters(
|
||
const nsTArray<nsCString>& aUnsafeHeaders,
|
||
bool aShouldStripRequestBodyHeader, bool aShouldStripAuthHeader) {
|
||
MOZ_RELEASE_ASSERT(!LoadRequestObserversCalled());
|
||
|
||
StoreRequireCORSPreflight(true);
|
||
mUnsafeHeaders = aUnsafeHeaders.Clone();
|
||
if (aShouldStripRequestBodyHeader || aShouldStripAuthHeader) {
|
||
mUnsafeHeaders.RemoveElementsBy([&](const nsCString& aHeader) {
|
||
return (aShouldStripRequestBodyHeader &&
|
||
(aHeader.LowerCaseEqualsASCII("content-type") ||
|
||
aHeader.LowerCaseEqualsASCII("content-encoding") ||
|
||
aHeader.LowerCaseEqualsASCII("content-language") ||
|
||
aHeader.LowerCaseEqualsASCII("content-location"))) ||
|
||
(aShouldStripAuthHeader &&
|
||
aHeader.LowerCaseEqualsASCII("authorization"));
|
||
});
|
||
}
|
||
}
|
||
|
||
void HttpBaseChannel::SetAltDataForChild(bool aIsForChild) {
|
||
StoreAltDataForChild(aIsForChild);
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetBlockAuthPrompt(bool* aValue) {
|
||
if (!aValue) {
|
||
return NS_ERROR_FAILURE;
|
||
}
|
||
|
||
*aValue = LoadBlockAuthPrompt();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetBlockAuthPrompt(bool aValue) {
|
||
ENSURE_CALLED_BEFORE_CONNECT();
|
||
|
||
StoreBlockAuthPrompt(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(nsIURI* aNewURI,
|
||
uint32_t aRedirectFlags) const {
|
||
if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
|
||
// for internal redirect due to auth retry we do not have any limit
|
||
// as we might restrict the number of times a user might retry
|
||
// authentication
|
||
if (aRedirectFlags & nsIChannelEventSink::REDIRECT_AUTH_RETRY) {
|
||
return NS_OK;
|
||
}
|
||
// 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;
|
||
}
|
||
|
||
// in case https-only mode is enabled which upgrades top-level requests to
|
||
// https and the page answers with a redirect (meta, 302, win.location, ...)
|
||
// then this method can break the cycle which causes the https-only exception
|
||
// page to appear. Note that https-first mode breaks upgrade downgrade endless
|
||
// loops within ShouldUpgradeHttpsFirstRequest because https-first does not
|
||
// display an exception page but needs a soft fallback/downgrade.
|
||
if (nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
|
||
mURI, aNewURI, mLoadInfo,
|
||
{nsHTTPSOnlyUtils::UpgradeDowngradeEndlessLoopOptions::
|
||
EnforceForHTTPSOnlyMode})) {
|
||
// Mark that we didn't upgrade to https due to loop detection in https-only
|
||
// mode to show https-only error page. We know that we are in https-only
|
||
// mode, because we passed `EnforceForHTTPSOnlyMode` to
|
||
// `IsUpgradeDowngradeEndlessLoop`. In other words we upgrade the request
|
||
// with https-only mode, but then immediately cancel the request.
|
||
uint32_t httpsOnlyStatus = mLoadInfo->GetHttpsOnlyStatus();
|
||
if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_UNINITIALIZED) {
|
||
httpsOnlyStatus ^= nsILoadInfo::HTTPS_ONLY_UNINITIALIZED;
|
||
httpsOnlyStatus |=
|
||
nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED;
|
||
mLoadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
|
||
}
|
||
|
||
LOG(("upgrade downgrade redirect loop!\n"));
|
||
return NS_ERROR_REDIRECT_LOOP;
|
||
}
|
||
// in case of http-first mode we want to add an exception to disable the
|
||
// upgrade behavior if we have upgrade-downgrade loop to break the loop and
|
||
// load the http request next
|
||
if (mozilla::StaticPrefs::
|
||
dom_security_https_first_add_exception_on_failure() &&
|
||
nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
|
||
mURI, aNewURI, mLoadInfo,
|
||
{nsHTTPSOnlyUtils::UpgradeDowngradeEndlessLoopOptions::
|
||
EnforceForHTTPSFirstMode})) {
|
||
nsHTTPSOnlyUtils::AddHTTPSFirstException(mURI, mLoadInfo);
|
||
}
|
||
|
||
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);
|
||
const char* snifferType = [chan]() {
|
||
if (RefPtr<nsHttpChannel> httpChannel = do_QueryObject(chan)) {
|
||
switch (httpChannel->GetSnifferCategoryType()) {
|
||
case SnifferCategoryType::NetContent:
|
||
return NS_CONTENT_SNIFFER_CATEGORY;
|
||
case SnifferCategoryType::OpaqueResponseBlocking:
|
||
return NS_ORB_SNIFFER_CATEGORY;
|
||
case SnifferCategoryType::All:
|
||
return NS_CONTENT_AND_ORB_SNIFFER_CATEGORY;
|
||
default:
|
||
MOZ_ASSERT_UNREACHABLE("Unexpected SnifferCategoryType!");
|
||
}
|
||
}
|
||
|
||
return NS_CONTENT_SNIFFER_CATEGORY;
|
||
}();
|
||
|
||
nsAutoCString newType;
|
||
NS_SniffContent(snifferType, 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 (nsContentUtils::ComputeIsSecureContext(this)) {
|
||
ParseServerTimingHeader(mResponseHead, aServerTiming);
|
||
ParseServerTimingHeader(mResponseTrailers, aServerTiming);
|
||
}
|
||
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::CancelByURLClassifier(nsresult aErrorCode) {
|
||
MOZ_ASSERT(
|
||
UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
|
||
return Cancel(aErrorCode);
|
||
}
|
||
|
||
NS_IMETHODIMP HttpBaseChannel::SetIPv4Disabled() {
|
||
mCaps |= NS_HTTP_DISABLE_IPV4;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP HttpBaseChannel::SetIPv6Disabled() {
|
||
mCaps |= NS_HTTP_DISABLE_IPV6;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP HttpBaseChannel::GetResponseEmbedderPolicy(
|
||
bool aIsOriginTrialCoepCredentiallessEnabled,
|
||
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, aIsOriginTrialCoepCredentiallessEnabled);
|
||
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
|
||
|
||
nsCOMPtr<nsISFVService> sfv = GetSFVService();
|
||
|
||
nsCOMPtr<nsISFVItem> item;
|
||
nsresult rv = sfv->ParseItem(openerPolicy, getter_AddRefs(item));
|
||
if (NS_FAILED(rv)) {
|
||
return rv;
|
||
}
|
||
|
||
nsCOMPtr<nsISFVBareItem> value;
|
||
rv = item->GetValue(getter_AddRefs(value));
|
||
if (NS_FAILED(rv)) {
|
||
return rv;
|
||
}
|
||
|
||
nsCOMPtr<nsISFVToken> token = do_QueryInterface(value);
|
||
if (!token) {
|
||
return NS_ERROR_UNEXPECTED;
|
||
}
|
||
|
||
rv = token->GetValue(openerPolicy);
|
||
if (NS_FAILED(rv)) {
|
||
return rv;
|
||
}
|
||
|
||
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;
|
||
bool isCoepCredentiallessEnabled;
|
||
rv = mLoadInfo->GetIsOriginTrialCoepCredentiallessEnabledForTopLevel(
|
||
&isCoepCredentiallessEnabled);
|
||
if (!isCoepCredentiallessEnabled) {
|
||
nsAutoCString originTrialToken;
|
||
Unused << mResponseHead->GetHeader(nsHttp::OriginTrial, originTrialToken);
|
||
if (!originTrialToken.IsEmpty()) {
|
||
nsCOMPtr<nsIPrincipal> resultPrincipal;
|
||
rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
|
||
this, getter_AddRefs(resultPrincipal));
|
||
if (!NS_WARN_IF(NS_FAILED(rv))) {
|
||
OriginTrials trials;
|
||
trials.UpdateFromToken(NS_ConvertASCIItoUTF16(originTrialToken),
|
||
resultPrincipal);
|
||
if (trials.IsEnabled(OriginTrial::CoepCredentialless)) {
|
||
isCoepCredentiallessEnabled = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
NS_ENSURE_SUCCESS(rv, rv);
|
||
if (NS_SUCCEEDED(
|
||
GetResponseEmbedderPolicy(isCoepCredentiallessEnabled, &coep)) &&
|
||
(coep == nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP ||
|
||
coep == nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS)) {
|
||
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 (!LoadOnStartRequestCalled()) {
|
||
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 = LoadHasCrossOriginOpenerPolicyMismatch();
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetOriginAgentClusterHeader(bool* aValue) {
|
||
MOZ_ASSERT(XRE_IsParentProcess());
|
||
if (!mResponseHead) {
|
||
return NS_ERROR_NOT_AVAILABLE;
|
||
}
|
||
|
||
nsAutoCString content;
|
||
nsresult rv = mResponseHead->GetHeader(nsHttp::OriginAgentCluster, content);
|
||
if (NS_FAILED(rv)) {
|
||
return rv;
|
||
}
|
||
|
||
// Origin-Agent-Cluster = <boolean>
|
||
nsCOMPtr<nsISFVService> sfv = GetSFVService();
|
||
nsCOMPtr<nsISFVItem> item;
|
||
rv = sfv->ParseItem(content, getter_AddRefs(item));
|
||
if (NS_FAILED(rv)) {
|
||
return rv;
|
||
}
|
||
nsCOMPtr<nsISFVBareItem> value;
|
||
rv = item->GetValue(getter_AddRefs(value));
|
||
if (NS_FAILED(rv)) {
|
||
return rv;
|
||
}
|
||
nsCOMPtr<nsISFVBool> flag = do_QueryInterface(value);
|
||
if (!flag) {
|
||
return NS_ERROR_NOT_AVAILABLE;
|
||
}
|
||
return flag->GetValue(aValue);
|
||
}
|
||
|
||
void HttpBaseChannel::MaybeFlushConsoleReports() {
|
||
// Flush if we have a known window ID.
|
||
if (mLoadInfo->GetInnerWindowID() > 0) {
|
||
FlushReportsToConsole(mLoadInfo->GetInnerWindowID());
|
||
return;
|
||
}
|
||
|
||
// 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() {}
|
||
|
||
bool HttpBaseChannel::Http3Allowed() const {
|
||
bool isDirectOrNoProxy =
|
||
mProxyInfo ? static_cast<nsProxyInfo*>(mProxyInfo.get())->IsDirect()
|
||
: true;
|
||
return !mUpgradeProtocolCallback && isDirectOrNoProxy &&
|
||
!(mCaps & NS_HTTP_BE_CONSERVATIVE) && !LoadBeConservative() &&
|
||
LoadAllowHttp3();
|
||
}
|
||
|
||
UniquePtr<nsHttpResponseHead>
|
||
HttpBaseChannel::MaybeCloneResponseHeadForCachedResource() {
|
||
if (!mResponseHead) {
|
||
return nullptr;
|
||
}
|
||
|
||
return MakeUnique<nsHttpResponseHead>(*mResponseHead);
|
||
}
|
||
|
||
void HttpBaseChannel::SetDummyChannelForCachedResource(
|
||
const nsHttpResponseHead* aMaybeResponseHead /* = nullptr */) {
|
||
mDummyChannelForCachedResource = true;
|
||
MOZ_ASSERT(!mResponseHead,
|
||
"SetDummyChannelForCachedResource should only be called once");
|
||
if (aMaybeResponseHead) {
|
||
mResponseHead = MakeUnique<nsHttpResponseHead>(*aMaybeResponseHead);
|
||
} else {
|
||
mResponseHead = MakeUnique<nsHttpResponseHead>();
|
||
}
|
||
}
|
||
|
||
void HttpBaseChannel::SetEarlyHints(
|
||
nsTArray<EarlyHintConnectArgs>&& aEarlyHints) {
|
||
mEarlyHints = std::move(aEarlyHints);
|
||
}
|
||
|
||
nsTArray<EarlyHintConnectArgs>&& HttpBaseChannel::TakeEarlyHints() {
|
||
return std::move(mEarlyHints);
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetEarlyHintPreloaderId(uint64_t aEarlyHintPreloaderId) {
|
||
mEarlyHintPreloaderId = aEarlyHintPreloaderId;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetEarlyHintPreloaderId(uint64_t* aEarlyHintPreloaderId) {
|
||
NS_ENSURE_ARG_POINTER(aEarlyHintPreloaderId);
|
||
*aEarlyHintPreloaderId = mEarlyHintPreloaderId;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetClassicScriptHintCharset(
|
||
const nsAString& aClassicScriptHintCharset) {
|
||
mClassicScriptHintCharset = aClassicScriptHintCharset;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP HttpBaseChannel::GetClassicScriptHintCharset(
|
||
nsAString& aClassicScriptHintCharset) {
|
||
aClassicScriptHintCharset = mClassicScriptHintCharset;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP HttpBaseChannel::SetDocumentCharacterSet(
|
||
const nsAString& aDocumentCharacterSet) {
|
||
mDocumentCharacterSet = aDocumentCharacterSet;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP HttpBaseChannel::GetDocumentCharacterSet(
|
||
nsAString& aDocumentCharacterSet) {
|
||
aDocumentCharacterSet = mDocumentCharacterSet;
|
||
return NS_OK;
|
||
}
|
||
|
||
void HttpBaseChannel::SetConnectionInfo(nsHttpConnectionInfo* aCI) {
|
||
mConnectionInfo = aCI ? aCI->Clone() : nullptr;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetIsProxyUsed(bool* aIsProxyUsed) {
|
||
if (mProxyInfo) {
|
||
if (!static_cast<nsProxyInfo*>(mProxyInfo.get())->IsDirect()) {
|
||
StoreIsProxyUsed(true);
|
||
}
|
||
}
|
||
*aIsProxyUsed = LoadIsProxyUsed();
|
||
return NS_OK;
|
||
}
|
||
|
||
static void CollectORBBlockTelemetry(
|
||
const OpaqueResponseBlockedTelemetryReason aTelemetryReason,
|
||
ExtContentPolicy aPolicy) {
|
||
glean::orb::block_reason.EnumGet(aTelemetryReason).Add();
|
||
|
||
switch (aPolicy) {
|
||
case ExtContentPolicy::TYPE_INVALID:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eInvalid)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_OTHER:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eOther)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_FETCH:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eBlockedFetch)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_SCRIPT:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eScript)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_JSON:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eJson)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_IMAGE:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eImage)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_STYLESHEET:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eStylesheet)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_XMLHTTPREQUEST:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eXmlhttprequest)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_DTD:
|
||
glean::orb::block_initiator.EnumGet(glean::orb::BlockInitiatorLabel::eDtd)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_FONT:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eFont)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_MEDIA:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eMedia)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_CSP_REPORT:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eCspReport)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_XSLT:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eXslt)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_IMAGESET:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eImageset)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_WEB_MANIFEST:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eWebManifest)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_SPECULATIVE:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eSpeculative)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_UA_FONT:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eUaFont)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eProxiedWebrtcMedia)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_PING:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::ePing)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_BEACON:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eBeacon)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_WEB_TRANSPORT:
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eWebTransport)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_WEB_IDENTITY:
|
||
// Don't bother extending the telemetry for this.
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eOther)
|
||
.Add();
|
||
break;
|
||
case ExtContentPolicy::TYPE_DOCUMENT:
|
||
case ExtContentPolicy::TYPE_SUBDOCUMENT:
|
||
case ExtContentPolicy::TYPE_OBJECT:
|
||
case ExtContentPolicy::TYPE_WEBSOCKET:
|
||
case ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD:
|
||
MOZ_ASSERT_UNREACHABLE("Shouldn't block this type");
|
||
// DOCUMENT, SUBDOCUMENT, OBJECT,
|
||
// WEBSOCKET and SAVEAS_DOWNLOAD are excluded from ORB
|
||
glean::orb::block_initiator
|
||
.EnumGet(glean::orb::BlockInitiatorLabel::eExcluded)
|
||
.Add();
|
||
break;
|
||
// Do not add default: so that compilers can catch the missing case.
|
||
}
|
||
}
|
||
|
||
void HttpBaseChannel::LogORBError(
|
||
const nsAString& aReason,
|
||
const OpaqueResponseBlockedTelemetryReason aTelemetryReason) {
|
||
auto policy = mLoadInfo->GetExternalContentPolicyType();
|
||
CollectORBBlockTelemetry(aTelemetryReason, policy);
|
||
|
||
// Blocking `ExtContentPolicy::TYPE_BEACON` isn't web observable, so keep
|
||
// quiet in the console about blocking it.
|
||
if (policy == ExtContentPolicy::TYPE_BEACON) {
|
||
return;
|
||
}
|
||
|
||
RefPtr<dom::Document> doc;
|
||
mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
|
||
|
||
nsAutoCString uri;
|
||
nsresult rv = nsContentUtils::AnonymizeURI(mURI, uri);
|
||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||
return;
|
||
}
|
||
|
||
uint64_t contentWindowId;
|
||
GetTopLevelContentWindowId(&contentWindowId);
|
||
if (contentWindowId) {
|
||
nsContentUtils::ReportToConsoleByWindowID(
|
||
u"A resource is blocked by OpaqueResponseBlocking, please check browser console for details."_ns,
|
||
nsIScriptError::warningFlag, "ORB"_ns, contentWindowId,
|
||
SourceLocation(mURI.get()));
|
||
}
|
||
|
||
AutoTArray<nsString, 2> params;
|
||
params.AppendElement(NS_ConvertUTF8toUTF16(uri));
|
||
params.AppendElement(aReason);
|
||
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "ORB"_ns, doc,
|
||
nsContentUtils::eNECKO_PROPERTIES,
|
||
"ResourceBlockedORB", params);
|
||
}
|
||
|
||
NS_IMETHODIMP HttpBaseChannel::SetEarlyHintLinkType(
|
||
uint32_t aEarlyHintLinkType) {
|
||
mEarlyHintLinkType = aEarlyHintLinkType;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP HttpBaseChannel::GetEarlyHintLinkType(
|
||
uint32_t* aEarlyHintLinkType) {
|
||
*aEarlyHintLinkType = mEarlyHintLinkType;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetHasContentDecompressed(bool aValue) {
|
||
LOG(("HttpBaseChannel::SetHasContentDecompressed [this=%p value=%d]\n", this,
|
||
aValue));
|
||
mHasContentDecompressed = aValue;
|
||
return NS_OK;
|
||
}
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetHasContentDecompressed(bool* value) {
|
||
*value = mHasContentDecompressed;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::SetRenderBlocking(bool aRenderBlocking) {
|
||
mRenderBlocking = aRenderBlocking;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP
|
||
HttpBaseChannel::GetRenderBlocking(bool* aRenderBlocking) {
|
||
*aRenderBlocking = mRenderBlocking;
|
||
return NS_OK;
|
||
}
|
||
|
||
NS_IMETHODIMP HttpBaseChannel::GetLastTransportStatus(
|
||
nsresult* aLastTransportStatus) {
|
||
return NS_ERROR_NOT_IMPLEMENTED;
|
||
}
|
||
|
||
void HttpBaseChannel::SetFetchPriorityDOM(
|
||
mozilla::dom::FetchPriority aPriority) {
|
||
switch (aPriority) {
|
||
case mozilla::dom::FetchPriority::Auto:
|
||
SetFetchPriority(nsIClassOfService::FETCHPRIORITY_AUTO);
|
||
return;
|
||
case mozilla::dom::FetchPriority::High:
|
||
SetFetchPriority(nsIClassOfService::FETCHPRIORITY_HIGH);
|
||
return;
|
||
case mozilla::dom::FetchPriority::Low:
|
||
SetFetchPriority(nsIClassOfService::FETCHPRIORITY_LOW);
|
||
return;
|
||
default:
|
||
MOZ_ASSERT_UNREACHABLE();
|
||
}
|
||
}
|
||
|
||
} // namespace net
|
||
} // namespace mozilla
|