/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// HttpLog.h should generally be included first
#include "mozilla/net/HttpBaseChannel.h"
#include <algorithm>
#include <utility>
#include "HttpBaseChannel.h"
#include "HttpLog.h"
#include "LoadInfo.h"
#include "ReferrerInfo.h"
#include "mozIRemoteLazyInputStream.h"
#include "mozIThirdPartyUtil.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/AntiTrackingUtils.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/BinarySearch.h"
#include "mozilla/ConsoleReportCollector.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/InputStreamLengthHelper.h"
#include "mozilla/Mutex.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/PermissionManager.h"
#include "mozilla/Components.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_fission.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/glean/NetwerkProtocolHttpMetrics.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Tokenizer.h"
#include "mozilla/browser/NimbusFeatures.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/FetchPriority.h"
#include "mozilla/dom/nsHTTPSOnlyUtils.h"
#include "mozilla/dom/nsMixedContentBlocker.h"
#include "mozilla/dom/Performance.h"
#include "mozilla/dom/PerformanceStorage.h"
#include "mozilla/dom/ProcessIsolation.h"
#include "mozilla/dom/RequestBinding.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/net/OpaqueResponseUtils.h"
#include "mozilla/net/UrlClassifierCommon.h"
#include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "nsBufferedStreams.h"
#include "nsCOMPtr.h"
#include "nsCRT.h"
#include "nsContentSecurityManager.h"
#include "nsContentSecurityUtils.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsEscape.h"
#include "nsGlobalWindowInner.h"
#include "nsGlobalWindowOuter.h"
#include "nsHttpChannel.h"
#include "nsHTTPCompressConv.h"
#include "nsHttpHandler.h"
#include "nsICacheInfoChannel.h"
#include "nsICachingChannel.h"
#include "nsIChannelEventSink.h"
#include "nsIConsoleService.h"
#include "nsIContentPolicy.h"
#include "nsICookieService.h"
#include "nsIDOMWindowUtils.h"
#include "nsIDocShell.h"
#include "nsIDNSService.h"
#include "nsIEncodedChannel.h"
#include "nsIHttpHeaderVisitor.h"
#include "nsILoadGroupChild.h"
#include "nsIMIMEInputStream.h"
#include "nsIMultiplexInputStream.h"
#include "nsIMutableArray.h"
#include "nsINetworkInterceptController.h"
#include "nsIObserverService.h"
#include "nsIPrincipal.h"
#include "nsIProtocolProxyService.h"
#include "nsIScriptError.h"
#include "nsIScriptSecurityManager.h"
#include "nsISecurityConsoleMessage.h"
#include "nsISeekableStream.h"
#include "nsIStorageStream.h"
#include "nsIStreamConverterService.h"
#include "nsITimedChannel.h"
#include "nsITransportSecurityInfo.h"
#include "nsIURIMutator.h"
#include "nsMimeTypes.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsPIDOMWindow.h"
#include "nsProxyRelease.h"
#include "nsReadableUtils.h"
#include "nsRedirectHistoryEntry.h"
#include "nsServerTiming.h"
#include "nsStreamListenerWrapper.h"
#include "nsStreamUtils.h"
#include "nsString.h"
#include "nsThreadUtils.h"
#include "nsURLHelper.h"
#include "mozilla/RemoteLazyInputStreamChild.h"
#include "mozilla/net/SFVService.h"
#include "mozilla/dom/ContentChild.h"
#include "nsQueryObject.h"
using mozilla::dom::RequestMode;
#define LOGORB(msg, ...) \
MOZ_LOG(GetORBLog(), LogLevel::Debug, \
(
"%s: %p " msg, __func__,
this,
##__VA_ARGS__))
namespace mozilla {
namespace net {
static bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom
const& aHeader) {
// IMPORTANT: keep this list ASCII-code sorted
static nsHttpAtomLiteral
const* blackList[] = {
&nsHttp::Accept,
&nsHttp::Accept_Encoding,
&nsHttp::Accept_Language,
&nsHttp::Alternate_Service_Used,
&nsHttp::Authentication,
&nsHttp::Authorization,
&nsHttp::Connection,
&nsHttp::Content_Length,
&nsHttp::Cookie,
&nsHttp::Host,
&nsHttp::
If,
&nsHttp::If_Match,
&nsHttp::If_Modified_Since,
&nsHttp::If_None_Match,
&nsHttp::If_None_Match_Any,
&nsHttp::If_Range,
&nsHttp::If_Unmodified_Since,
&nsHttp::Proxy_Authenticate,
&nsHttp::Proxy_Authorization,
&nsHttp::Range,
&nsHttp::TE,
&nsHttp::Transfer_Encoding,
&nsHttp::Upgrade,
&nsHttp::User_Agent,
&nsHttp::WWW_Authenticate};
class HttpAtomComparator {
nsHttpAtom
const& mTarget;
public:
explicit HttpAtomComparator(nsHttpAtom
const& aTarget) : mTarget(aTarget) {}
int operator()(nsHttpAtom
const* aVal)
const {
if (mTarget == *aVal) {
return 0;
}
return strcmp(mTarget.get(), aVal->get());
}
int operator()(nsHttpAtomLiteral
const* aVal)
const {
if (mTarget == *aVal) {
return 0;
}
return strcmp(mTarget.get(), aVal->get());
}
};
size_t unused;
return BinarySearchIf(blackList, 0, std::size(blackList),
HttpAtomComparator(aHeader), &unused);
}
class AddHeadersToChannelVisitor final :
public nsIHttpHeaderVisitor {
public:
NS_DECL_ISUPPORTS
explicit AddHeadersToChannelVisitor(nsIHttpChannel* aChannel)
: mChannel(aChannel) {}
NS_IMETHOD VisitHeader(
const nsACString& aHeader,
const nsACString& aValue) override {
nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
if (!IsHeaderBlacklistedForRedirectCopy(atom)) {
DebugOnly<nsresult> rv =
mChannel->SetRequestHeader(aHeader, aValue,
false);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
return NS_OK;
}
private:
~AddHeadersToChannelVisitor() =
default;
nsCOMPtr<nsIHttpChannel> mChannel;
};
NS_IMPL_ISUPPORTS(AddHeadersToChannelVisitor, nsIHttpHeaderVisitor)
static OpaqueResponseFilterFetch ConfiguredFilterFetchResponseBehaviour() {
uint32_t pref = StaticPrefs::
browser_opaqueResponseBlocking_filterFetchResponse_DoNotUseDirectly();
if (NS_WARN_IF(pref >
static_cast<uint32_t>(OpaqueResponseFilterFetch::All))) {
return OpaqueResponseFilterFetch::All;
}
return static_cast<OpaqueResponseFilterFetch>(pref);
}
HttpBaseChannel::HttpBaseChannel()
: mReportCollector(
new ConsoleReportCollector()),
mHttpHandler(gHttpHandler),
mChannelCreationTime(0),
mComputedCrossOriginOpenerPolicy(nsILoadInfo::OPENER_POLICY_UNSAFE_NONE),
mStartPos(UINT64_MAX),
mTransferSize(0),
mRequestSize(0),
mDecodedBodySize(0),
mSupportsHTTP3(
false),
mEncodedBodySize(0),
mRequestContextID(0),
mContentWindowId(0),
mBrowserId(0),
mAltDataLength(-1),
mChannelId(0),
mReqContentLength(0U),
mStatus(NS_OK),
mCanceled(
false),
mFirstPartyClassificationFlags(0),
mThirdPartyClassificationFlags(0),
mLoadFlags(LOAD_NORMAL),
mCaps(0),
mClassOfService(0,
false),
mTlsFlags(0),
mSuspendCount(0),
mInitialRwin(0),
mProxyResolveFlags(0),
mContentDispositionHint(UINT32_MAX),
mRequestMode(RequestMode::No_cors),
mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW),
mLastRedirectFlags(0),
mPriority(PRIORITY_NORMAL),
mRedirectionLimit(gHttpHandler->RedirectionLimit()),
mRedirectCount(0),
mInternalRedirectCount(0),
mCachedOpaqueResponseBlockingPref(
StaticPrefs::browser_opaqueResponseBlocking()),
mChannelBlockedByOpaqueResponse(
false),
mDummyChannelForCachedResource(
false),
mHasContentDecompressed(
false),
mRenderBlocking(
false) {
StoreApplyConversion(
true);
StoreAllowSTS(
true);
StoreTracingEnabled(
true);
StoreReportTiming(
true);
StoreAllowSpdy(
true);
StoreAllowHttp3(
true);
StoreAllowAltSvc(
true);
StoreResponseTimeoutEnabled(
true);
StoreAllRedirectsSameOrigin(
true);
StoreAllRedirectsPassTimingAllowCheck(
true);
StoreUpgradableToSecure(
true);
StoreIsUserAgentHeaderModified(
false);
this->mSelfAddr.inet = {};
this->mPeerAddr.inet = {};
LOG((
"Creating HttpBaseChannel @%p\n",
this));
// Subfields of unions cannot be targeted in an initializer list.
#ifdef MOZ_VALGRIND
// Zero the entire unions so that Valgrind doesn't complain when we send them
// to another process.
memset(&mSelfAddr, 0,
sizeof(NetAddr));
memset(&mPeerAddr, 0,
sizeof(NetAddr));
#endif
mSelfAddr.raw.family = PR_AF_UNSPEC;
mPeerAddr.raw.family = PR_AF_UNSPEC;
}
HttpBaseChannel::~HttpBaseChannel() {
LOG((
"Destroying HttpBaseChannel @%p\n",
this));
// Make sure we don't leak
CleanRedirectCacheChainIfNecessary();
ReleaseMainThreadOnlyReferences();
}
namespace {
// anon
class NonTailRemover :
public nsISupports {
NS_DECL_THREADSAFE_ISUPPORTS
explicit NonTailRemover(nsIRequestContext* rc) : mRequestContext(rc) {}
private:
virtual ~NonTailRemover() {
MOZ_ASSERT(NS_IsMainThread());
mRequestContext->RemoveNonTailRequest();
}
nsCOMPtr<nsIRequestContext> mRequestContext;
};
NS_IMPL_ISUPPORTS0(NonTailRemover)
}
// namespace
void HttpBaseChannel::ReleaseMainThreadOnlyReferences() {
if (NS_IsMainThread()) {
// Already on main thread, let dtor to
// take care of releasing references
RemoveAsNonTailRequest();
return;
}
nsTArray<nsCOMPtr<nsISupports>> arrayToRelease;
arrayToRelease.AppendElement(mLoadGroup.forget());
arrayToRelease.AppendElement(mLoadInfo.forget());
arrayToRelease.AppendElement(mCallbacks.forget());
arrayToRelease.AppendElement(mProgressSink.forget());
arrayToRelease.AppendElement(mPrincipal.forget());
arrayToRelease.AppendElement(mListener.forget());
arrayToRelease.AppendElement(mCompressListener.forget());
arrayToRelease.AppendElement(mORB.forget());
if (LoadAddedAsNonTailRequest()) {
// RemoveNonTailRequest() on our request context must be called on the main
// thread
MOZ_RELEASE_ASSERT(mRequestContext,
"Someone released rc or set flags w/o having it?");
nsCOMPtr<nsISupports> nonTailRemover(
new NonTailRemover(mRequestContext));
arrayToRelease.AppendElement(nonTailRemover.forget());
}
NS_DispatchToMainThread(
new ProxyReleaseRunnable(std::move(arrayToRelease)));
}
void HttpBaseChannel::AddClassificationFlags(uint32_t aClassificationFlags,
bool aIsThirdParty) {
LOG(
(
"HttpBaseChannel::AddClassificationFlags classificationFlags=%d "
"thirdparty=%d %p",
aClassificationFlags,
static_cast<
int>(aIsThirdParty),
this));
if (aIsThirdParty) {
mThirdPartyClassificationFlags |= aClassificationFlags;
}
else {
mFirstPartyClassificationFlags |= aClassificationFlags;
}
}
static bool isSecureOrTrustworthyURL(nsIURI* aURI) {
return aURI->SchemeIs(
"https") ||
(StaticPrefs::network_http_encoding_trustworthy_is_https() &&
nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(aURI));
}
nsresult HttpBaseChannel::Init(nsIURI* aURI, uint32_t aCaps,
nsProxyInfo* aProxyInfo,
uint32_t aProxyResolveFlags, nsIURI* aProxyURI,
uint64_t aChannelId,
ExtContentPolicyType aContentPolicyType,
nsILoadInfo* aLoadInfo) {
LOG1((
"HttpBaseChannel::Init [this=%p]\n",
this));
MOZ_ASSERT(aURI,
"null uri");
mURI = aURI;
mOriginalURI = aURI;
mDocumentURI = nullptr;
mCaps = aCaps;
mProxyResolveFlags = aProxyResolveFlags;
mProxyURI = aProxyURI;
mChannelId = aChannelId;
mLoadInfo = aLoadInfo;
// Construct connection info object
nsAutoCString host;
int32_t port = -1;
bool isHTTPS = isSecureOrTrustworthyURL(mURI);
nsresult rv = mURI->GetAsciiHost(host);
if (NS_FAILED(rv))
return rv;
// Reject the URL if it doesn't specify a host
if (host.IsEmpty())
return NS_ERROR_MALFORMED_URI;
rv = mURI->GetPort(&port);
if (NS_FAILED(rv))
return rv;
LOG1((
"host=%s port=%d\n", host.get(), port));
rv = mURI->GetAsciiSpec(mSpec);
if (NS_FAILED(rv))
return rv;
LOG1((
"uri=%s\n", mSpec.get()));
// Assert default request method
MOZ_ASSERT(mRequestHead.EqualsMethod(nsHttpRequestHead::kMethod_Get));
// Set request headers
nsAutoCString hostLine;
rv = nsHttpHandler::GenerateHostPort(host, port, hostLine);
if (NS_FAILED(rv))
return rv;
rv = mRequestHead.SetHeader(nsHttp::Host, hostLine);
if (NS_FAILED(rv))
return rv;
rv = gHttpHandler->AddStandardRequestHeaders(
&mRequestHead, isHTTPS, aContentPolicyType,
nsContentUtils::ShouldResistFingerprinting(
this,
RFPTarget::HttpUserAgent));
if (NS_FAILED(rv))
return rv;
nsAutoCString type;
if (aProxyInfo && NS_SUCCEEDED(aProxyInfo->GetType(type)) &&
!type.EqualsLiteral(
"unknown")) {
mProxyInfo = aProxyInfo;
}
mCurrentThread = GetCurrentSerialEventTarget();
return rv;
}
//-----------------------------------------------------------------------------
// HttpBaseChannel::nsISupports
//-----------------------------------------------------------------------------
NS_IMPL_ADDREF(HttpBaseChannel)
NS_IMPL_RELEASE(HttpBaseChannel)
NS_INTERFACE_MAP_BEGIN(HttpBaseChannel)
NS_INTERFACE_MAP_ENTRY(nsIRequest)
NS_INTERFACE_MAP_ENTRY(nsIChannel)
NS_INTERFACE_MAP_ENTRY(nsIIdentChannel)
NS_INTERFACE_MAP_ENTRY(nsIEncodedChannel)
NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
NS_INTERFACE_MAP_ENTRY(nsIForcePendingChannel)
NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
NS_INTERFACE_MAP_ENTRY(nsIFormPOSTActionChannel)
NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
NS_INTERFACE_MAP_ENTRY(nsIPrivateBrowsingChannel)
NS_INTERFACE_MAP_ENTRY(nsITimedChannel)
NS_INTERFACE_MAP_ENTRY(nsIConsoleReportCollector)
NS_INTERFACE_MAP_ENTRY(nsIThrottledInputChannel)
NS_INTERFACE_MAP_ENTRY(nsIClassifiedChannel)
NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpBaseChannel)
NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)
//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIRequest
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpBaseChannel::GetName(nsACString& aName) {
aName = mSpec;
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::IsPending(
bool* aIsPending) {
NS_ENSURE_ARG_POINTER(aIsPending);
*aIsPending = LoadIsPending() || LoadForcePending();
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::GetStatus(nsresult* aStatus) {
NS_ENSURE_ARG_POINTER(aStatus);
*aStatus = mStatus;
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
NS_ENSURE_ARG_POINTER(aLoadGroup);
*aLoadGroup = do_AddRef(mLoadGroup).take();
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
MOZ_ASSERT(NS_IsMainThread(),
"Should only be called on the main thread.");
if (!CanSetLoadGroup(aLoadGroup)) {
return NS_ERROR_FAILURE;
}
mLoadGroup = aLoadGroup;
mProgressSink = nullptr;
UpdatePrivateBrowsing();
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
NS_ENSURE_ARG_POINTER(aLoadFlags);
*aLoadFlags = mLoadFlags;
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
mLoadFlags = aLoadFlags;
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
if (!LoadIsOCSP()) {
return GetTRRModeImpl(aTRRMode);
}
nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
nsIDNSService::ResolverMode trrMode = nsIDNSService::MODE_NATIVEONLY;
// If this is an OCSP channel, and the global TRR mode is TRR_ONLY (3)
// then we set the mode for this channel as TRR_DISABLED_MODE.
// We do this to prevent a TRR service channel's OCSP validation from
// blocking DNS resolution completely.
if (dns && NS_SUCCEEDED(dns->GetCurrentTrrMode(&trrMode)) &&
trrMode == nsIDNSService::MODE_TRRONLY) {
*aTRRMode = nsIRequest::TRR_DISABLED_MODE;
return NS_OK;
}
return GetTRRModeImpl(aTRRMode);
}
NS_IMETHODIMP
HttpBaseChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
return SetTRRModeImpl(aTRRMode);
}
NS_IMETHODIMP
HttpBaseChannel::SetDocshellUserAgentOverride() {
RefPtr<dom::BrowsingContext> bc;
MOZ_ALWAYS_SUCCEEDS(mLoadInfo->GetBrowsingContext(getter_AddRefs(bc)));
if (!bc) {
return NS_OK;
}
nsAutoString customUserAgent;
bc->GetCustomUserAgent(customUserAgent);
if (customUserAgent.IsEmpty() || customUserAgent.IsVoid()) {
return NS_OK;
}
NS_ConvertUTF16toUTF8 utf8CustomUserAgent(customUserAgent);
nsresult rv = SetRequestHeaderInternal(
"User-Agent"_ns, utf8CustomUserAgent,
false,
nsHttpHeaderArray::eVarietyRequestEnforceDefault);
if (NS_FAILED(rv)) {
return rv;
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpBaseChannel::GetOriginalURI(nsIURI** aOriginalURI) {
NS_ENSURE_ARG_POINTER(aOriginalURI);
*aOriginalURI = do_AddRef(mOriginalURI).take();
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::SetOriginalURI(nsIURI* aOriginalURI) {
ENSURE_CALLED_BEFORE_CONNECT();
NS_ENSURE_ARG_POINTER(aOriginalURI);
mOriginalURI = aOriginalURI;
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::GetURI(nsIURI** aURI) {
NS_ENSURE_ARG_POINTER(aURI);
*aURI = do_AddRef(mURI).take();
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::GetOwner(nsISupports** aOwner) {
NS_ENSURE_ARG_POINTER(aOwner);
*aOwner = do_AddRef(mOwner).take();
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::SetOwner(nsISupports* aOwner) {
mOwner = aOwner;
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
MOZ_RELEASE_ASSERT(aLoadInfo,
"loadinfo can't be null");
mLoadInfo = aLoadInfo;
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
*aLoadInfo = do_AddRef(mLoadInfo).take();
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::GetIsDocument(
bool* aIsDocument) {
return NS_GetIsDocumentChannel(
this, aIsDocument);
}
NS_IMETHODIMP
HttpBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
*aCallbacks = do_AddRef(mCallbacks).take();
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
MOZ_ASSERT(NS_IsMainThread(),
"Should only be called on the main thread.");
if (!CanSetCallbacks(aCallbacks)) {
return NS_ERROR_FAILURE;
}
mCallbacks = aCallbacks;
mProgressSink = nullptr;
UpdatePrivateBrowsing();
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::GetContentType(nsACString& aContentType) {
if (!mResponseHead) {
aContentType.Truncate();
return NS_ERROR_NOT_AVAILABLE;
}
mResponseHead->ContentType(aContentType);
if (!aContentType.IsEmpty()) {
return NS_OK;
}
aContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::SetContentType(
const nsACString& aContentType) {
if (mListener || LoadWasOpened() || mDummyChannelForCachedResource) {
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
nsAutoCString contentTypeBuf, charsetBuf;
bool hadCharset;
net_ParseContentType(aContentType, contentTypeBuf, charsetBuf, &hadCharset);
mResponseHead->SetContentType(contentTypeBuf);
// take care not to stomp on an existing charset
if (hadCharset) mResponseHead->SetContentCharset(charsetBuf);
}
else {
// We are being given a content-type hint.
bool dummy;
net_ParseContentType(aContentType, mContentTypeHint, mContentCharsetHint,
&dummy);
}
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::GetContentCharset(nsACString& aContentCharset) {
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
mResponseHead->ContentCharset(aContentCharset);
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::SetContentCharset(
const nsACString& aContentCharset) {
if (mListener) {
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
mResponseHead->SetContentCharset(aContentCharset);
}
else {
// Charset hint
mContentCharsetHint = aContentCharset;
}
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::GetContentDisposition(uint32_t* aContentDisposition) {
// See bug 1658877. If mContentDispositionHint is already
// DISPOSITION_ATTACHMENT, it means this channel is created from a
// download attribute. In this case, we should prefer the value from the
// download attribute rather than the value in content disposition header.
// DISPOSITION_FORCE_INLINE is used to explicitly set inline, used by
// the pdf reader when loading a attachment pdf without having to
// download it.
if (mContentDispositionHint == nsIChannel::DISPOSITION_ATTACHMENT ||
mContentDispositionHint == nsIChannel::DISPOSITION_FORCE_INLINE) {
*aContentDisposition = mContentDispositionHint;
return NS_OK;
}
nsresult rv;
nsCString header;
rv = GetContentDispositionHeader(header);
if (NS_FAILED(rv)) {
if (mContentDispositionHint == UINT32_MAX)
return rv;
*aContentDisposition = mContentDispositionHint;
return NS_OK;
}
*aContentDisposition = NS_GetContentDispositionFromHeader(header,
this);
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::SetContentDisposition(uint32_t aContentDisposition) {
mContentDispositionHint = aContentDisposition;
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::GetContentDispositionFilename(
nsAString& aContentDispositionFilename) {
aContentDispositionFilename.Truncate();
nsresult rv;
nsCString header;
rv = GetContentDispositionHeader(header);
if (NS_SUCCEEDED(rv)) {
rv = NS_GetFilenameFromDisposition(aContentDispositionFilename, header);
}
// If we failed to get the filename from header, we should use
// mContentDispositionFilename, since mContentDispositionFilename is set from
// the download attribute.
if (NS_FAILED(rv)) {
if (!mContentDispositionFilename) {
return rv;
}
aContentDispositionFilename = *mContentDispositionFilename;
return NS_OK;
}
return rv;
}
NS_IMETHODIMP
HttpBaseChannel::SetContentDispositionFilename(
const nsAString& aContentDispositionFilename) {
mContentDispositionFilename =
MakeUnique<nsString>(aContentDispositionFilename);
// For safety reasons ensure the filename doesn't contain null characters and
// replace them with underscores. We may later pass the extension to system
// MIME APIs that expect null terminated strings.
mContentDispositionFilename->ReplaceChar(char16_t(0),
'_');
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::GetContentDispositionHeader(
nsACString& aContentDispositionHeader) {
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
nsresult rv = mResponseHead->GetHeader(nsHttp::Content_Disposition,
aContentDispositionHeader);
if (NS_FAILED(rv) || aContentDispositionHeader.IsEmpty()) {
return NS_ERROR_NOT_AVAILABLE;
}
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::GetContentLength(int64_t* aContentLength) {
NS_ENSURE_ARG_POINTER(aContentLength);
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
if (LoadDeliveringAltData()) {
MOZ_ASSERT(!mAvailableCachedAltDataType.IsEmpty());
*aContentLength = mAltDataLength;
return NS_OK;
}
*aContentLength = mResponseHead->ContentLength();
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::SetContentLength(int64_t value) {
if (!mDummyChannelForCachedResource) {
MOZ_ASSERT_UNREACHABLE(
"HttpBaseChannel::SetContentLength");
return NS_ERROR_NOT_IMPLEMENTED;
}
MOZ_ASSERT(mResponseHead);
mResponseHead->SetContentLength(value);
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::Open(nsIInputStream** aStream) {
if (!gHttpHandler->Active()) {
LOG((
"HttpBaseChannel::Open after HTTP shutdown..."));
return NS_ERROR_NOT_AVAILABLE;
}
nsCOMPtr<nsIStreamListener> listener;
nsresult rv =
nsContentSecurityManager::doContentSecurityCheck(
this, listener);
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_IN_PROGRESS);
if (!gHttpHandler->Active()) {
LOG((
"HttpBaseChannel::Open after HTTP shutdown..."));
return NS_ERROR_NOT_AVAILABLE;
}
return NS_ImplementChannelOpen(
this, aStream);
}
//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIUploadChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpBaseChannel::GetUploadStream(nsIInputStream** stream) {
NS_ENSURE_ARG_POINTER(stream);
*stream = do_AddRef(mUploadStream).take();
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::SetUploadStream(nsIInputStream* stream,
const nsACString& contentTypeArg,
int64_t contentLength) {
// NOTE: for backwards compatibility and for compatibility with old style
// plugins, |stream| may include headers, specifically Content-Type and
// Content-Length headers. in this case, |contentType| and |contentLength|
// would be unspecified. this is traditionally the case of a POST request,
// and so we select POST as the request method if contentType and
// contentLength are unspecified.
if (stream) {
nsAutoCString method;
bool hasHeaders =
false;
// This method and ExplicitSetUploadStream mean different things by "empty
// content type string". This method means "no header", but
// ExplicitSetUploadStream means "header with empty value". So we have to
// massage the contentType argument into the form ExplicitSetUploadStream
// expects.
nsCOMPtr<nsIMIMEInputStream> mimeStream;
nsCString contentType(contentTypeArg);
if (contentType.IsEmpty()) {
contentType.SetIsVoid(
true);
method =
"POST"_ns;
// MIME streams are a special case, and include headers which need to be
// copied to the channel.
mimeStream = do_QueryInterface(stream);
if (mimeStream) {
// Copy non-origin related headers to the channel.
nsCOMPtr<nsIHttpHeaderVisitor> visitor =
new AddHeadersToChannelVisitor(
this);
mimeStream->VisitHeaders(visitor);
return ExplicitSetUploadStream(stream, contentType, contentLength,
method, hasHeaders);
}
hasHeaders =
true;
}
else {
method =
"PUT"_ns;
MOZ_ASSERT(
NS_FAILED(CallQueryInterface(stream, getter_AddRefs(mimeStream))),
"nsIMIMEInputStream should not be set with an explicit content type");
}
return ExplicitSetUploadStream(stream, contentType, contentLength, method,
hasHeaders);
}
// if stream is null, ExplicitSetUploadStream returns error.
// So we need special case for GET method.
StoreUploadStreamHasHeaders(
false);
SetRequestMethod(
"GET"_ns);
// revert to GET request
mUploadStream = nullptr;
return NS_OK;
}
namespace {
class MIMEHeaderCopyVisitor final :
public nsIHttpHeaderVisitor {
public:
explicit MIMEHeaderCopyVisitor(nsIMIMEInputStream* aDest) : mDest(aDest) {}
NS_DECL_ISUPPORTS
NS_IMETHOD VisitHeader(
const nsACString& aName,
const nsACString& aValue) override {
return mDest->AddHeader(PromiseFlatCString(aName).get(),
PromiseFlatCString(aValue).get());
}
private:
~MIMEHeaderCopyVisitor() =
default;
nsCOMPtr<nsIMIMEInputStream> mDest;
};
NS_IMPL_ISUPPORTS(MIMEHeaderCopyVisitor, nsIHttpHeaderVisitor)
static void NormalizeCopyComplete(
void* aClosure, nsresult aStatus) {
#ifdef DEBUG
// Called on the STS thread by NS_AsyncCopy
nsCOMPtr<nsIEventTarget> sts =
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
bool result =
false;
sts->IsOnCurrentThread(&result);
MOZ_ASSERT(result,
"Should only be called on the STS thread.");
#endif
RefPtr<GenericPromise::
Private> ready =
already_AddRefed(
static_cast<GenericPromise::
Private*>(aClosure));
if (NS_SUCCEEDED(aStatus)) {
ready->Resolve(
true, __func__);
}
else {
ready->Reject(aStatus, __func__);
}
}
// Normalize the upload stream for a HTTP channel, so that is one of the
// expected and compatible types. Components like WebExtensions and DevTools
// expect that upload streams in the parent process are cloneable, seekable, and
// synchronous to read, which this function helps guarantee somewhat efficiently
// and without loss of information.
//
// If the replacement stream outparameter is not initialized to `nullptr`, the
// returned stream should be used instead of `aUploadStream` as the upload
// stream for the HTTP channel, and the previous stream should not be touched
// again.
//
// If aReadyPromise is non-nullptr after the function is called, it is a promise
// which should be awaited before continuing to `AsyncOpen` the HTTP channel,
// as the replacement stream will not be ready until it is resolved.
static nsresult NormalizeUploadStream(nsIInputStream* aUploadStream,
nsIInputStream** aReplacementStream,
GenericPromise** aReadyPromise) {
MOZ_ASSERT(XRE_IsParentProcess());
*aReplacementStream = nullptr;
*aReadyPromise = nullptr;
// Unwrap RemoteLazyInputStream and normalize the contents as we're in the
// parent process.
if (nsCOMPtr<mozIRemoteLazyInputStream> lazyStream =
do_QueryInterface(aUploadStream)) {
nsCOMPtr<nsIInputStream> internal;
if (NS_SUCCEEDED(
lazyStream->TakeInternalStream(getter_AddRefs(internal)))) {
nsCOMPtr<nsIInputStream> replacement;
nsresult rv = NormalizeUploadStream(internal, getter_AddRefs(replacement),
aReadyPromise);
NS_ENSURE_SUCCESS(rv, rv);
if (replacement) {
replacement.forget(aReplacementStream);
}
else {
internal.forget(aReplacementStream);
}
return NS_OK;
}
}
// Preserve MIME information on the stream when normalizing.
if (nsCOMPtr<nsIMIMEInputStream> mime = do_QueryInterface(aUploadStream)) {
nsCOMPtr<nsIInputStream> data;
nsresult rv = mime->GetData(getter_AddRefs(data));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> replacement;
rv =
NormalizeUploadStream(data, getter_AddRefs(replacement), aReadyPromise);
NS_ENSURE_SUCCESS(rv, rv);
if (replacement) {
nsCOMPtr<nsIMIMEInputStream> replacementMime(
do_CreateInstance(
"@mozilla.org/network/mime-input-stream;1", &rv));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIHttpHeaderVisitor> visitor =
new MIMEHeaderCopyVisitor(replacementMime);
rv = mime->VisitHeaders(visitor);
NS_ENSURE_SUCCESS(rv, rv);
rv = replacementMime->SetData(replacement);
NS_ENSURE_SUCCESS(rv, rv);
replacementMime.forget(aReplacementStream);
}
return NS_OK;
}
// Preserve "real" buffered input streams which wrap data (i.e. are backed by
// nsBufferedInputStream), but normalize the wrapped stream.
if (nsCOMPtr<nsIBufferedInputStream> buffered =
do_QueryInterface(aUploadStream)) {
nsCOMPtr<nsIInputStream> data;
if (NS_SUCCEEDED(buffered->GetData(getter_AddRefs(data)))) {
nsCOMPtr<nsIInputStream> replacement;
nsresult rv = NormalizeUploadStream(data, getter_AddRefs(replacement),
aReadyPromise);
NS_ENSURE_SUCCESS(rv, rv);
if (replacement) {
// This buffer size should be kept in sync with HTMLFormSubmission.
rv = NS_NewBufferedInputStream(aReplacementStream, replacement.forget(),
8192);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
}
// Preserve multiplex input streams, normalizing each individual inner stream
// to avoid unnecessary copying.
if (nsCOMPtr<nsIMultiplexInputStream> multiplex =
do_QueryInterface(aUploadStream)) {
uint32_t count = multiplex->GetCount();
nsTArray<nsCOMPtr<nsIInputStream>> streams(count);
nsTArray<RefPtr<GenericPromise>> promises(count);
bool replace =
false;
for (uint32_t i = 0; i < count; ++i) {
nsCOMPtr<nsIInputStream> inner;
nsresult rv = multiplex->GetStream(i, getter_AddRefs(inner));
NS_ENSURE_SUCCESS(rv, rv);
RefPtr<GenericPromise> promise;
nsCOMPtr<nsIInputStream> replacement;
rv = NormalizeUploadStream(inner, getter_AddRefs(replacement),
getter_AddRefs(promise));
NS_ENSURE_SUCCESS(rv, rv);
if (promise) {
promises.AppendElement(promise);
}
if (replacement) {
streams.AppendElement(replacement);
replace =
true;
}
else {
streams.AppendElement(inner);
}
}
// If any of the inner streams needed to be replaced, replace the entire
// nsIMultiplexInputStream.
if (replace) {
nsresult rv;
nsCOMPtr<nsIMultiplexInputStream> replacement =
do_CreateInstance(
"@mozilla.org/io/multiplex-input-stream;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
for (
auto& stream : streams) {
rv = replacement->AppendStream(stream);
NS_ENSURE_SUCCESS(rv, rv);
}
MOZ_ALWAYS_SUCCEEDS(CallQueryInterface(replacement, aReplacementStream));
}
// Wait for all inner promises to settle before resolving the final promise.
if (!promises.IsEmpty()) {
RefPtr<GenericPromise> ready =
GenericPromise::AllSettled(GetCurrentSerialEventTarget(), promises)
->Then(GetCurrentSerialEventTarget(), __func__,
[](GenericPromise::AllSettledPromiseType::
ResolveOrRejectValue&& aResults)
-> RefPtr<GenericPromise> {
MOZ_ASSERT(aResults.IsResolve(),
"AllSettled never rejects");
for (
auto& result : aResults.ResolveValue()) {
if (result.IsReject()) {
return GenericPromise::CreateAndReject(
result.RejectValue(), __func__);
}
}
return GenericPromise::CreateAndResolve(
true, __func__);
});
ready.forget(aReadyPromise);
}
return NS_OK;
}
// If the stream is cloneable, seekable and non-async, we can allow it. Async
// input streams can cause issues, as various consumers of input streams
// expect the payload to be synchronous and `Available()` to be the length of
// the stream, which is not true for asynchronous streams.
nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(aUploadStream);
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aUploadStream);
if (NS_InputStreamIsCloneable(aUploadStream) && seekable && !async) {
return NS_OK;
}
// Asynchronously copy our non-normalized stream into a StorageStream so that
// it is seekable, cloneable, and synchronous once the copy completes.
NS_WARNING(
"Upload Stream is being copied into StorageStream");
nsCOMPtr<nsIStorageStream> storageStream;
nsresult rv =
NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storageStream));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIOutputStream> sink;
rv = storageStream->GetOutputStream(0, getter_AddRefs(sink));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> replacementStream;
rv = storageStream->NewInputStream(0, getter_AddRefs(replacementStream));
NS_ENSURE_SUCCESS(rv, rv);
// Ensure the source stream is buffered before starting the copy so we can use
// ReadSegments, as nsStorageStream doesn't implement WriteSegments.
nsCOMPtr<nsIInputStream> source = aUploadStream;
if (!NS_InputStreamIsBuffered(aUploadStream)) {
nsCOMPtr<nsIInputStream> bufferedSource;
rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedSource),
source.forget(), 4096);
NS_ENSURE_SUCCESS(rv, rv);
source = bufferedSource.forget();
}
// Perform an AsyncCopy into the input stream on the STS.
nsCOMPtr<nsIEventTarget> target =
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
RefPtr<GenericPromise::
Private> ready =
new GenericPromise::
Private(__func__);
rv = NS_AsyncCopy(source, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS, 4096,
NormalizeCopyComplete, do_AddRef(ready).take());
if (NS_WARN_IF(NS_FAILED(rv))) {
ready.get()->Release();
return rv;
}
replacementStream.forget(aReplacementStream);
ready.forget(aReadyPromise);
return NS_OK;
}
}
// anonymous namespace
NS_IMETHODIMP
HttpBaseChannel::CloneUploadStream(int64_t* aContentLength,
nsIInputStream** aClonedStream) {
NS_ENSURE_ARG_POINTER(aContentLength);
NS_ENSURE_ARG_POINTER(aClonedStream);
*aClonedStream = nullptr;
if (!XRE_IsParentProcess()) {
NS_WARNING(
"CloneUploadStream is only supported in the parent process");
return NS_ERROR_NOT_AVAILABLE;
}
if (!mUploadStream) {
return NS_OK;
}
nsCOMPtr<nsIInputStream> clonedStream;
nsresult rv =
NS_CloneInputStream(mUploadStream, getter_AddRefs(clonedStream));
NS_ENSURE_SUCCESS(rv, rv);
clonedStream.forget(aClonedStream);
*aContentLength = mReqContentLength;
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIUploadChannel2
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpBaseChannel::ExplicitSetUploadStream(nsIInputStream* aStream,
const nsACString& aContentType,
int64_t aContentLength,
const nsACString& aMethod,
bool aStreamHasHeaders) {
// Ensure stream is set and method is valid
NS_ENSURE_TRUE(aStream, NS_ERROR_FAILURE);
{
DebugOnly<nsCOMPtr<nsIMIMEInputStream>> mimeStream;
MOZ_ASSERT(
!aStreamHasHeaders || NS_FAILED(CallQueryInterface(
aStream, getter_AddRefs(mimeStream.value))),
"nsIMIMEInputStream should not include headers");
}
nsresult rv = SetRequestMethod(aMethod);
NS_ENSURE_SUCCESS(rv, rv);
if (!aStreamHasHeaders && !aContentType.IsVoid()) {
if (aContentType.IsEmpty()) {
SetEmptyRequestHeader(
"Content-Type"_ns);
}
else {
SetRequestHeader(
"Content-Type"_ns, aContentType,
false);
}
}
StoreUploadStreamHasHeaders(aStreamHasHeaders);
return InternalSetUploadStream(aStream, aContentLength, !aStreamHasHeaders);
}
nsresult HttpBaseChannel::InternalSetUploadStream(
nsIInputStream* aUploadStream, int64_t aContentLength,
bool aSetContentLengthHeader) {
// If we're not on the main thread, such as for TRR, the content length must
// be provided, as we can't normalize our upload stream.
if (!NS_IsMainThread()) {
if (aContentLength < 0) {
MOZ_ASSERT_UNREACHABLE(
"Upload content length must be explicit off-main-thread");
return NS_ERROR_INVALID_ARG;
}
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aUploadStream);
if (!NS_InputStreamIsCloneable(aUploadStream) || !seekable) {
MOZ_ASSERT_UNREACHABLE(
"Upload stream must be cloneable & seekable off-main-thread");
return NS_ERROR_INVALID_ARG;
}
mUploadStream = aUploadStream;
ExplicitSetUploadStreamLength(aContentLength, aSetContentLengthHeader);
return NS_OK;
}
// Normalize the upload stream we're provided to ensure that it is cloneable,
// seekable, and synchronous when in the parent process.
//
// This might be an async operation, in which case ready will be returned and
// resolved when the operation is complete.
nsCOMPtr<nsIInputStream> replacement;
RefPtr<GenericPromise> ready;
if (XRE_IsParentProcess()) {
nsresult rv = NormalizeUploadStream(
aUploadStream, getter_AddRefs(replacement), getter_AddRefs(ready));
NS_ENSURE_SUCCESS(rv, rv);
}
mUploadStream = replacement ? replacement.get() : aUploadStream;
// Once the upload stream is ready, fetch its length before proceeding with
// AsyncOpen.
auto onReady = [self = RefPtr{
this}, aContentLength, aSetContentLengthHeader,
stream = mUploadStream]() {
auto setLengthAndResume = [self, aSetContentLengthHeader](int64_t aLength) {
self->StorePendingUploadStreamNormalization(
false);
self->ExplicitSetUploadStreamLength(aLength >= 0 ? aLength : 0,
aSetContentLengthHeader);
self->MaybeResumeAsyncOpen();
};
if (aContentLength >= 0) {
setLengthAndResume(aContentLength);
return;
}
int64_t length;
if (InputStreamLengthHelper::GetSyncLength(stream, &length)) {
setLengthAndResume(length);
return;
}
InputStreamLengthHelper::GetAsyncLength(stream, setLengthAndResume);
};
StorePendingUploadStreamNormalization(
true);
// Resolve onReady synchronously unless a promise is returned.
if (ready) {
ready->Then(GetCurrentSerialEventTarget(), __func__,
[onReady = std::move(onReady)](
GenericPromise::ResolveOrRejectValue&&) { onReady(); });
}
else {
onReady();
}
return NS_OK;
}
void HttpBaseChannel::ExplicitSetUploadStreamLength(
uint64_t aContentLength,
bool aSetContentLengthHeader) {
// We already have the content length. We don't need to determinate it.
mReqContentLength = aContentLength;
if (!aSetContentLengthHeader) {
return;
}
nsAutoCString header;
header.AssignLiteral(
"Content-Length");
// Maybe the content-length header has been already set.
nsAutoCString value;
nsresult rv = GetRequestHeader(header, value);
if (NS_SUCCEEDED(rv) && !value.IsEmpty()) {
return;
}
nsAutoCString contentLengthStr;
contentLengthStr.AppendInt(aContentLength);
SetRequestHeader(header, contentLengthStr,
false);
}
NS_IMETHODIMP
HttpBaseChannel::GetUploadStreamHasHeaders(
bool* hasHeaders) {
NS_ENSURE_ARG(hasHeaders);
*hasHeaders = LoadUploadStreamHasHeaders();
return NS_OK;
}
bool HttpBaseChannel::MaybeWaitForUploadStreamNormalization(
nsIStreamListener* aListener, nsISupports* aContext) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!LoadAsyncOpenWaitingForStreamNormalization(),
"AsyncOpen() called twice?");
if (!LoadPendingUploadStreamNormalization()) {
return false;
}
mListener = aListener;
StoreAsyncOpenWaitingForStreamNormalization(
true);
return true;
}
void HttpBaseChannel::MaybeResumeAsyncOpen() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(!LoadPendingUploadStreamNormalization());
if (!LoadAsyncOpenWaitingForStreamNormalization()) {
return;
}
nsCOMPtr<nsIStreamListener> listener;
listener.swap(mListener);
StoreAsyncOpenWaitingForStreamNormalization(
false);
nsresult rv = AsyncOpen(listener);
if (NS_WARN_IF(NS_FAILED(rv))) {
DoAsyncAbort(rv);
}
}
//-----------------------------------------------------------------------------
// HttpBaseChannel::nsIEncodedChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpBaseChannel::GetApplyConversion(
bool* value) {
*value = LoadApplyConversion();
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::SetApplyConversion(
bool value) {
LOG((
"HttpBaseChannel::SetApplyConversion [this=%p value=%d]\n",
this,
value));
StoreApplyConversion(value);
return NS_OK;
}
nsresult HttpBaseChannel::DoApplyContentConversions(
nsIStreamListener* aNextListener, nsIStreamListener** aNewNextListener) {
return DoApplyContentConversions(aNextListener, aNewNextListener, nullptr);
}
// create a listener chain that looks like this
// http-channel -> decompressor (n times) -> InterceptFailedOnSTop ->
// channel-creator-listener
//
// we need to do this because not every decompressor has fully streamed output
// so may need a call to OnStopRequest to identify its completion state.. and if
// it creates an error there the channel status code needs to be updated before
// calling the terminal listener. Having the decompress do it via cancel() means
// channels cannot effectively be used in two contexts (specifically this one
// and a peek context for sniffing)
//
class InterceptFailedOnStop :
public nsIThreadRetargetableStreamListener {
virtual ~InterceptFailedOnStop() =
default;
nsCOMPtr<nsIStreamListener> mNext;
HttpBaseChannel* mChannel;
public:
InterceptFailedOnStop(nsIStreamListener* arg, HttpBaseChannel* chan)
: mNext(arg), mChannel(chan) {}
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override {
return mNext->OnStartRequest(aRequest);
}
NS_IMETHOD OnStopRequest(nsIRequest* aRequest,
nsresult aStatusCode) override {
if (NS_FAILED(aStatusCode) && NS_SUCCEEDED(mChannel->mStatus)) {
LOG((
"HttpBaseChannel::InterceptFailedOnStop %p seting status %" PRIx32,
mChannel,
static_cast<uint32_t>(aStatusCode)));
mChannel->mStatus = aStatusCode;
}
return mNext->OnStopRequest(aRequest, aStatusCode);
}
NS_IMETHOD OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream,
uint64_t aOffset, uint32_t aCount) override {
return mNext->OnDataAvailable(aRequest, aInputStream, aOffset, aCount);
}
};
NS_IMPL_ADDREF(InterceptFailedOnStop)
NS_IMPL_RELEASE(InterceptFailedOnStop)
NS_INTERFACE_MAP_BEGIN(InterceptFailedOnStop)
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver)
NS_INTERFACE_MAP_END
NS_IMETHODIMP
InterceptFailedOnStop::CheckListenerChain() {
nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
do_QueryInterface(mNext);
if (!listener) {
return NS_ERROR_NO_INTERFACE;
}
return listener->CheckListenerChain();
}
NS_IMETHODIMP
InterceptFailedOnStop::OnDataFinished(nsresult aStatus) {
nsCOMPtr<nsIThreadRetargetableStreamListener> listener =
do_QueryInterface(mNext);
if (listener) {
return listener->OnDataFinished(aStatus);
}
return NS_OK;
}
NS_IMETHODIMP
HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener,
nsIStreamListener** aNewNextListener,
nsISupports* aCtxt) {
*aNewNextListener = nullptr;
if (!mResponseHead || !aNextListener) {
return NS_OK;
}
LOG((
"HttpBaseChannel::DoApplyContentConversions [this=%p]\n",
this));
if (!LoadApplyConversion()) {
LOG((
"not applying conversion per ApplyConversion\n"));
return NS_OK;
}
if (LoadHasAppliedConversion()) {
LOG((
"not applying conversion because HasAppliedConversion is true\n"));
return NS_OK;
}
if (LoadDeliveringAltData()) {
MOZ_ASSERT(!mAvailableCachedAltDataType.IsEmpty());
LOG((
"not applying conversion because delivering alt-data\n"));
return NS_OK;
}
nsAutoCString contentEncoding;
nsresult rv =
mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
if (NS_FAILED(rv) || contentEncoding.IsEmpty())
return NS_OK;
nsCOMPtr<nsIStreamListener> nextListener =
new InterceptFailedOnStop(aNextListener,
this);
// The encodings are listed in the order they were applied
// (see rfc 2616 section 14.11), so they need to removed in reverse
// order. This is accomplished because the converter chain ends up
// being a stack with the last converter created being the first one
// to accept the raw network data.
char* cePtr = contentEncoding.BeginWriting();
uint32_t count = 0;
while (
char* val = nsCRT::strtok(cePtr, HTTP_LWS
",", &cePtr)) {
if (++count > 16) {
// That's ridiculous. We only understand 2 different ones :)
// but for compatibility with old code, we will just carry on without
// removing the encodings
LOG((
"Too many Content-Encodings. Ignoring remainder.\n"));
break;
}
if (gHttpHandler->IsAcceptableEncoding(val,
isSecureOrTrustworthyURL(mURI))) {
RefPtr<nsHTTPCompressConv> converter =
new nsHTTPCompressConv();
nsAutoCString from(val);
ToLowerCase(from);
rv = converter->AsyncConvertData(from.get(),
"uncompressed", nextListener,
aCtxt);
if (NS_FAILED(rv)) {
LOG((
"Unexpected failure of AsyncConvertData %s\n", val));
return rv;
}
LOG((
"converter removed '%s' content-encoding\n", val));
if (Telemetry::CanRecordPrereleaseData()) {
int mode = 0;
if (from.EqualsLiteral(
"gzip") || from.EqualsLiteral(
"x-gzip")) {
mode = 1;
}
else if (from.EqualsLiteral(
"deflate") ||
from.EqualsLiteral(
"x-deflate")) {
mode = 2;
}
else if (from.EqualsLiteral(
"br")) {
mode = 3;
}
else if (from.EqualsLiteral(
"zstd")) {
mode = 4;
}
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;
}
//-----------------------------------------------------------------------------
// 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",
--> --------------------
--> maximum size reached
--> --------------------