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/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/Telemetry.h"
#include "mozilla/Tokenizer.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/nsHTTPSOnlyUtils.h"
#include "mozilla/dom/nsMixedContentBlocker.h"
#include "mozilla/dom/Performance.h"
#include "mozilla/dom/PerformanceStorage.h"
#include "mozilla/dom/ProcessIsolation.h"
#include "mozilla/dom/RequestBinding.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/net/OpaqueResponseUtils.h"
#include "mozilla/net/UrlClassifierCommon.h"
#include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "nsBufferedStreams.h"
#include "nsCOMPtr.h"
#include "nsCRT.h"
#include "nsContentSecurityManager.h"
#include "nsContentSecurityUtils.h"
#include "nsContentUtils.h"
#include "nsEscape.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 "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;
namespace mozilla {
namespace net {
static bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader) {
// IMPORTANT: keep this list ASCII-code sorted
static nsHttpAtom const* blackList[] = {&nsHttp::Accept,
&nsHttp::Accept_Encoding,
&nsHttp::Accept_Language,
&nsHttp::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());
}
};
size_t unused;
return BinarySearchIf(blackList, 0, ArrayLength(blackList),
HttpAtomComparator(aHeader), &unused);
}
class AddHeadersToChannelVisitor final : public nsIHttpHeaderVisitor {
public:
NS_DECL_ISUPPORTS
explicit AddHeadersToChannelVisitor(nsIHttpChannel* aChannel)
: mChannel(aChannel) {}
NS_IMETHOD VisitHeader(const nsACString& aHeader,
const nsACString& aValue) override {
nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
if (!IsHeaderBlacklistedForRedirectCopy(atom)) {
DebugOnly<nsresult> rv =
mChannel->SetRequestHeader(aHeader, aValue, false);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
return NS_OK;
}
private:
~AddHeadersToChannelVisitor() = default;
nsCOMPtr<nsIHttpChannel> mChannel;
};
NS_IMPL_ISUPPORTS(AddHeadersToChannelVisitor, nsIHttpHeaderVisitor)
HttpBaseChannel::HttpBaseChannel()
: mReportCollector(new ConsoleReportCollector()),
mHttpHandler(gHttpHandler),
mChannelCreationTime(0),
mComputedCrossOriginOpenerPolicy(nsILoadInfo::OPENER_POLICY_UNSAFE_NONE),
mStartPos(UINT64_MAX),
mTransferSize(0),
mRequestSize(0),
mDecodedBodySize(0),
mSupportsHTTP3(false),
mEncodedBodySize(0),
mRequestContextID(0),
mContentWindowId(0),
mTopBrowsingContextId(0),
mAltDataLength(-1),
mChannelId(0),
mReqContentLength(0U),
mStatus(NS_OK),
mCanceled(false),
mFirstPartyClassificationFlags(0),
mThirdPartyClassificationFlags(0),
mLoadFlags(LOAD_NORMAL),
mCaps(0),
mClassOfService(0, false),
mTlsFlags(0),
mSuspendCount(0),
mInitialRwin(0),
mProxyResolveFlags(0),
mContentDispositionHint(UINT32_MAX),
mRequestMode(RequestMode::No_cors),
mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW),
mLastRedirectFlags(0),
mPriority(PRIORITY_NORMAL),
mRedirectionLimit(gHttpHandler->RedirectionLimit()),
mRedirectCount(0),
mInternalRedirectCount(0),
mCachedOpaqueResponseBlockingPref(
StaticPrefs::browser_opaqueResponseBlocking()),
mBlockOpaqueResponseAfterSniff(false),
mCheckIsOpaqueResponseAllowedAfterSniff(false),
mDummyChannelForImageCache(false) {
StoreApplyConversion(true);
StoreAllowSTS(true);
StoreTracingEnabled(true);
StoreReportTiming(true);
StoreAllowSpdy(true);
StoreAllowHttp3(true);
StoreAllowAltSvc(true);
StoreResponseTimeoutEnabled(true);
StoreAllRedirectsSameOrigin(true);
StoreAllRedirectsPassTimingAllowCheck(true);
StoreUpgradableToSecure(true);
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());
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));
if (NS_FAILED(rv)) return rv;
nsAutoCString type;
if (aProxyInfo && NS_SUCCEEDED(aProxyInfo->GetType(type)) &&
!type.EqualsLiteral("unknown")) {
mProxyInfo = aProxyInfo;
}
mCurrentThread = GetCurrentEventTarget();
return rv;
}
//-----------------------------------------------------------------------------
// HttpBaseChannel::nsISupports
//-----------------------------------------------------------------------------
NS_IMPL_ADDREF(HttpBaseChannel)
NS_IMPL_RELEASE(HttpBaseChannel)
NS_INTERFACE_MAP_BEGIN(HttpBaseChannel)
NS_INTERFACE_MAP_ENTRY(nsIRequest)
NS_INTERFACE_MAP_ENTRY(nsIChannel)
NS_INTERFACE_MAP_ENTRY(nsIIdentChannel)
NS_INTERFACE_MAP_ENTRY(nsIEncodedChannel)
NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
NS_INTERFACE_MAP_ENTRY(nsIForcePendingChannel)
NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
NS_INTERFACE_MAP_ENTRY(nsIFormPOSTActionChannel)
NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
NS_INTERFACE_MAP_ENTRY(nsIPrivateBrowsingChannel)
NS_INTERFACE_MAP_ENTRY(nsITimedChannel)
NS_INTERFACE_MAP_ENTRY(nsIConsoleReportCollector)
NS_INTERFACE_MAP_ENTRY(nsIThrottledInputChannel)
NS_INTERFACE_MAP_ENTRY(nsIClassifiedChannel)
NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpBaseChannel)
NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)
//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIRequest
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpBaseChannel::GetName(nsACString& aName) {
aName = mSpec;
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::IsPending(bool* aIsPending) {
NS_ENSURE_ARG_POINTER(aIsPending);
*aIsPending = 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 = SetRequestHeader("User-Agent"_ns, utf8CustomUserAgent, false);
if (NS_FAILED(rv)) return rv;
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpBaseChannel::GetOriginalURI(nsIURI** aOriginalURI) {
NS_ENSURE_ARG_POINTER(aOriginalURI);
*aOriginalURI = do_AddRef(mOriginalURI).take();
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::SetOriginalURI(nsIURI* aOriginalURI) {
ENSURE_CALLED_BEFORE_CONNECT();
NS_ENSURE_ARG_POINTER(aOriginalURI);
mOriginalURI = aOriginalURI;
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::GetURI(nsIURI** aURI) {
NS_ENSURE_ARG_POINTER(aURI);
*aURI = do_AddRef(mURI).take();
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::GetOwner(nsISupports** aOwner) {
NS_ENSURE_ARG_POINTER(aOwner);
*aOwner = 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() || mDummyChannelForImageCache) {
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 (!mDummyChannelForImageCache) {
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);
mRequestHead.SetMethod("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;
}
// SetRequestHeader propagates headers to chrome if HttpChannelChild
MOZ_ASSERT(!LoadWasOpened());
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 nsIStreamListener {
virtual ~InterceptFailedOnStop() = default;
nsCOMPtr<nsIStreamListener> mNext;
HttpBaseChannel* mChannel;
public:
InterceptFailedOnStop(nsIStreamListener* arg, HttpBaseChannel* chan)
: mNext(arg), mChannel(chan) {}
NS_DECL_THREADSAFE_ISUPPORTS
NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override {
return mNext->OnStartRequest(aRequest);
}
NS_IMETHOD OnStopRequest(nsIRequest* aRequest,
nsresult aStatusCode) override {
if (NS_FAILED(aStatusCode) && NS_SUCCEEDED(mChannel->mStatus)) {
LOG(("HttpBaseChannel::InterceptFailedOnStop %p seting status %" PRIx32,
mChannel, static_cast<uint32_t>(aStatusCode)));
mChannel->mStatus = aStatusCode;
}
return mNext->OnStopRequest(aRequest, aStatusCode);
}
NS_IMETHOD OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream,
uint64_t aOffset, uint32_t aCount) override {
return mNext->OnDataAvailable(aRequest, aInputStream, aOffset, aCount);
}
};
NS_IMPL_ISUPPORTS(InterceptFailedOnStop, nsIStreamListener, nsIRequestObserver)
NS_IMETHODIMP
HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener,
nsIStreamListener** aNewNextListener,
nsISupports* aCtxt) {
*aNewNextListener = nullptr;
if (!mResponseHead || !aNextListener) {
return NS_OK;
}
LOG(("HttpBaseChannel::DoApplyContentConversions [this=%p]\n", this));
if (!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;
}
Telemetry::Accumulate(Telemetry::HTTP_CONTENT_ENCODING, 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;