Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/PolicyContainer.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),
mClassOfService(0, false),
mRequestMode(RequestMode::No_cors),
mRedirectionLimit(gHttpHandler->RedirectionLimit()),
mCachedOpaqueResponseBlockingPref(
StaticPrefs::browser_opaqueResponseBlocking()) {
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;
// 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();
}
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;
}
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;
}
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;
}
// 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.
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:
// The `opaque-response-safelist check` is implemented in:
// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff`
// * `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff`
// * `OpaqueResponseBlocker::ValidateJavaScript`
OpaqueResponse
HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff() {
MOZ_ASSERT(XRE_IsParentProcess());
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`.
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`.
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);
// 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:
// 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);
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:
*/
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;
}
//-----------------------------------------------------------------------------
// 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
<