forked from mirrors/gecko-dev
Correctness improvements: * UTF errors are handled safely per spec instead of dangerously truncating strings. * There are fewer converter implementations. Performance improvements: * The old code did exact buffer length math, which meant doing UTF math twice on each input string (once for length calculation and another time for conversion). Exact length math is more complicated when handling errors properly, which the old code didn't do. The new code does UTF math on the string content only once (when converting) but risks allocating more than once. There are heuristics in place to lower the probability of reallocation in cases where the double math avoidance isn't enough of a saving to absorb an allocation and memcpy. * Previously, in UTF-16 <-> UTF-8 conversions, an ASCII prefix was optimized but a single non-ASCII code point pessimized the rest of the string. The new code tries to get back on the fast ASCII path. * UTF-16 to Latin1 conversion guarantees less about handling of out-of-range input to eliminate an operation from the inner loop on x86/x86_64. * When assigning to a pre-existing string, the new code tries to reuse the old buffer instead of first releasing the old buffer and then allocating a new one. * When reallocating from the new code, the memcpy covers only the data that is part of the logical length of the old string instead of memcpying the whole capacity. (For old callers old excess memcpy behavior is preserved due to bogus callers. See bug 1472113.) * UTF-8 strings in XPConnect that are in the Latin1 range are passed to SpiderMonkey as Latin1. New features: * Conversion between UTF-8 and Latin1 is added in order to enable faster future interop between Rust code (or otherwise UTF-8-using code) and text node and SpiderMonkey code that uses Latin1. MozReview-Commit-ID: JaJuExfILM9
4698 lines
132 KiB
C++
4698 lines
132 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set sw=2 ts=8 et tw=80 : */
|
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
// HttpLog.h should generally be included first
|
|
#include "HttpLog.h"
|
|
|
|
#include "mozilla/net/HttpBaseChannel.h"
|
|
|
|
#include "nsGlobalWindowOuter.h"
|
|
#include "nsHttpHandler.h"
|
|
#include "nsMimeTypes.h"
|
|
#include "nsNetCID.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsReadableUtils.h"
|
|
|
|
#include "nsICachingChannel.h"
|
|
#include "nsIPrincipal.h"
|
|
#include "nsIScriptError.h"
|
|
#include "nsISeekableStream.h"
|
|
#include "nsIStorageStream.h"
|
|
#include "nsITimedChannel.h"
|
|
#include "nsIEncodedChannel.h"
|
|
#include "nsIApplicationCacheChannel.h"
|
|
#include "nsIMutableArray.h"
|
|
#include "nsEscape.h"
|
|
#include "nsStreamListenerWrapper.h"
|
|
#include "nsISecurityConsoleMessage.h"
|
|
#include "nsURLHelper.h"
|
|
#include "nsICookieService.h"
|
|
#include "nsIStreamConverterService.h"
|
|
#include "nsCRT.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsIMutableArray.h"
|
|
#include "nsIScriptSecurityManager.h"
|
|
#include "nsIObserverService.h"
|
|
#include "nsProxyRelease.h"
|
|
#include "nsPIDOMWindow.h"
|
|
#include "nsIDocShell.h"
|
|
#include "nsINetworkInterceptController.h"
|
|
#include "mozilla/dom/Performance.h"
|
|
#include "mozilla/dom/PerformanceStorage.h"
|
|
#include "mozilla/NullPrincipal.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozIThirdPartyUtil.h"
|
|
#include "nsStreamUtils.h"
|
|
#include "nsThreadUtils.h"
|
|
#include "nsContentSecurityManager.h"
|
|
#include "nsIChannelEventSink.h"
|
|
#include "nsILoadGroupChild.h"
|
|
#include "mozilla/ConsoleReportCollector.h"
|
|
#include "LoadInfo.h"
|
|
#include "nsISSLSocketControl.h"
|
|
#include "mozilla/Telemetry.h"
|
|
#include "nsIURL.h"
|
|
#include "nsIConsoleService.h"
|
|
#include "mozilla/BinarySearch.h"
|
|
#include "mozilla/DebugOnly.h"
|
|
#include "mozilla/Move.h"
|
|
#include "mozilla/net/PartiallySeekableInputStream.h"
|
|
#include "mozilla/InputStreamLengthHelper.h"
|
|
#include "nsIHttpHeaderVisitor.h"
|
|
#include "nsIMIMEInputStream.h"
|
|
#include "nsIXULRuntime.h"
|
|
#include "nsICacheInfoChannel.h"
|
|
#include "nsIDOMWindowUtils.h"
|
|
#include "nsHttpChannel.h"
|
|
#include "nsRedirectHistoryEntry.h"
|
|
#include "nsServerTiming.h"
|
|
|
|
#include <algorithm>
|
|
#include "HttpBaseChannel.h"
|
|
|
|
namespace mozilla {
|
|
namespace net {
|
|
|
|
static
|
|
bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader)
|
|
{
|
|
// IMPORTANT: keep this list ASCII-code sorted
|
|
static nsHttpAtom const* blackList[] = {
|
|
&nsHttp::Accept,
|
|
&nsHttp::Accept_Encoding,
|
|
&nsHttp::Accept_Language,
|
|
&nsHttp::Authentication,
|
|
&nsHttp::Authorization,
|
|
&nsHttp::Connection,
|
|
&nsHttp::Content_Length,
|
|
&nsHttp::Cookie,
|
|
&nsHttp::Host,
|
|
&nsHttp::If,
|
|
&nsHttp::If_Match,
|
|
&nsHttp::If_Modified_Since,
|
|
&nsHttp::If_None_Match,
|
|
&nsHttp::If_None_Match_Any,
|
|
&nsHttp::If_Range,
|
|
&nsHttp::If_Unmodified_Since,
|
|
&nsHttp::Proxy_Authenticate,
|
|
&nsHttp::Proxy_Authorization,
|
|
&nsHttp::Range,
|
|
&nsHttp::TE,
|
|
&nsHttp::Transfer_Encoding,
|
|
&nsHttp::Upgrade,
|
|
&nsHttp::User_Agent,
|
|
&nsHttp::WWW_Authenticate
|
|
};
|
|
|
|
class HttpAtomComparator
|
|
{
|
|
nsHttpAtom const& mTarget;
|
|
public:
|
|
explicit HttpAtomComparator(nsHttpAtom const& aTarget)
|
|
: mTarget(aTarget) {}
|
|
int operator()(nsHttpAtom const* aVal) const {
|
|
if (mTarget == *aVal) {
|
|
return 0;
|
|
}
|
|
return strcmp(mTarget._val, aVal->_val);
|
|
}
|
|
};
|
|
|
|
size_t unused;
|
|
return BinarySearchIf(blackList, 0, ArrayLength(blackList),
|
|
HttpAtomComparator(aHeader), &unused);
|
|
}
|
|
|
|
class AddHeadersToChannelVisitor final : public nsIHttpHeaderVisitor
|
|
{
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
|
|
explicit AddHeadersToChannelVisitor(nsIHttpChannel *aChannel)
|
|
: mChannel(aChannel)
|
|
{
|
|
}
|
|
|
|
NS_IMETHOD VisitHeader(const nsACString& aHeader,
|
|
const nsACString& aValue) override
|
|
{
|
|
nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
|
|
if (!IsHeaderBlacklistedForRedirectCopy(atom)) {
|
|
DebugOnly<nsresult> rv = mChannel->SetRequestHeader(aHeader, aValue, false);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}
|
|
return NS_OK;
|
|
}
|
|
private:
|
|
~AddHeadersToChannelVisitor() = default;
|
|
|
|
nsCOMPtr<nsIHttpChannel> mChannel;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(AddHeadersToChannelVisitor, nsIHttpHeaderVisitor)
|
|
|
|
HttpBaseChannel::HttpBaseChannel()
|
|
: mReportCollector(new ConsoleReportCollector())
|
|
, mHttpHandler(gHttpHandler)
|
|
, mChannelCreationTime(0)
|
|
, mStartPos(UINT64_MAX)
|
|
, mTransferSize(0)
|
|
, mDecodedBodySize(0)
|
|
, mEncodedBodySize(0)
|
|
, mRequestContextID(0)
|
|
, mContentWindowId(0)
|
|
, mTopLevelOuterContentWindowId(0)
|
|
, mAltDataLength(0)
|
|
, mChannelId(0)
|
|
, mReqContentLength(0U)
|
|
, mStatus(NS_OK)
|
|
, mCanceled(false)
|
|
, mIsTrackingResource(false)
|
|
, mLoadFlags(LOAD_NORMAL)
|
|
, mCaps(0)
|
|
, mClassOfService(0)
|
|
, mUpgradeToSecure(false)
|
|
, mApplyConversion(true)
|
|
, mIsPending(false)
|
|
, mWasOpened(false)
|
|
, mRequestObserversCalled(false)
|
|
, mResponseHeadersModified(false)
|
|
, mAllowSTS(true)
|
|
, mThirdPartyFlags(0)
|
|
, mUploadStreamHasHeaders(false)
|
|
, mInheritApplicationCache(true)
|
|
, mChooseApplicationCache(false)
|
|
, mLoadedFromApplicationCache(false)
|
|
, mChannelIsForDownload(false)
|
|
, mTracingEnabled(true)
|
|
, mTimingEnabled(false)
|
|
, mReportTiming(true)
|
|
, mAllowSpdy(true)
|
|
, mAllowAltSvc(true)
|
|
, mBeConservative(false)
|
|
, mTRR(false)
|
|
, mResponseTimeoutEnabled(true)
|
|
, mAllRedirectsSameOrigin(true)
|
|
, mAllRedirectsPassTimingAllowCheck(true)
|
|
, mResponseCouldBeSynthesized(false)
|
|
, mBlockAuthPrompt(false)
|
|
, mAllowStaleCacheContent(false)
|
|
, mAddedAsNonTailRequest(false)
|
|
, mAsyncOpenWaitingForStreamLength(false)
|
|
, mUpgradableToSecure(true)
|
|
, mTlsFlags(0)
|
|
, mSuspendCount(0)
|
|
, mInitialRwin(0)
|
|
, mProxyResolveFlags(0)
|
|
, mContentDispositionHint(UINT32_MAX)
|
|
, mReferrerPolicy(NS_GetDefaultReferrerPolicy())
|
|
, mCorsMode(nsIHttpChannelInternal::CORS_MODE_NO_CORS)
|
|
, mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW)
|
|
, mLastRedirectFlags(0)
|
|
, mPriority(PRIORITY_NORMAL)
|
|
, mRedirectionLimit(gHttpHandler->RedirectionLimit())
|
|
, mRedirectCount(0)
|
|
, mInternalRedirectCount(0)
|
|
, mAsyncOpenTimeOverriden(false)
|
|
, mForcePending(false)
|
|
, mCorsIncludeCredentials(false)
|
|
, mOnStartRequestCalled(false)
|
|
, mOnStopRequestCalled(false)
|
|
, mAfterOnStartRequestBegun(false)
|
|
, mRequireCORSPreflight(false)
|
|
, mAltDataForChild(false)
|
|
, mForceMainDocumentChannel(false)
|
|
, mPendingInputStreamLengthOperation(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)
|
|
|
|
} // anon
|
|
|
|
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(mURI.forget());
|
|
arrayToRelease.AppendElement(mOriginalURI.forget());
|
|
arrayToRelease.AppendElement(mDocumentURI.forget());
|
|
arrayToRelease.AppendElement(mLoadGroup.forget());
|
|
arrayToRelease.AppendElement(mLoadInfo.forget());
|
|
arrayToRelease.AppendElement(mCallbacks.forget());
|
|
arrayToRelease.AppendElement(mProgressSink.forget());
|
|
arrayToRelease.AppendElement(mReferrer.forget());
|
|
arrayToRelease.AppendElement(mApplicationCache.forget());
|
|
arrayToRelease.AppendElement(mAPIRedirectToURI.forget());
|
|
arrayToRelease.AppendElement(mProxyURI.forget());
|
|
arrayToRelease.AppendElement(mPrincipal.forget());
|
|
arrayToRelease.AppendElement(mTopWindowURI.forget());
|
|
arrayToRelease.AppendElement(mListener.forget());
|
|
arrayToRelease.AppendElement(mListenerContext.forget());
|
|
arrayToRelease.AppendElement(mCompressListener.forget());
|
|
|
|
if (mAddedAsNonTailRequest) {
|
|
// RemoveNonTailRequest() on our request context must be called on the main thread
|
|
MOZ_RELEASE_ASSERT(mRequestContext, "Someone released rc or set flags w/o having it?");
|
|
|
|
nsCOMPtr<nsISupports> nonTailRemover(new NonTailRemover(mRequestContext));
|
|
arrayToRelease.AppendElement(nonTailRemover.forget());
|
|
}
|
|
|
|
NS_DispatchToMainThread(new ProxyReleaseRunnable(std::move(arrayToRelease)));
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::SetIsTrackingResource()
|
|
{
|
|
LOG(("HttpBaseChannel::SetIsTrackingResource %p", this));
|
|
mIsTrackingResource = true;
|
|
}
|
|
|
|
nsresult
|
|
HttpBaseChannel::Init(nsIURI *aURI,
|
|
uint32_t aCaps,
|
|
nsProxyInfo *aProxyInfo,
|
|
uint32_t aProxyResolveFlags,
|
|
nsIURI *aProxyURI,
|
|
uint64_t aChannelId)
|
|
{
|
|
LOG(("HttpBaseChannel::Init [this=%p]\n", this));
|
|
|
|
MOZ_ASSERT(aURI, "null uri");
|
|
|
|
mURI = aURI;
|
|
mOriginalURI = aURI;
|
|
mDocumentURI = nullptr;
|
|
mCaps = aCaps;
|
|
mProxyResolveFlags = aProxyResolveFlags;
|
|
mProxyURI = aProxyURI;
|
|
mChannelId = aChannelId;
|
|
|
|
// Construct connection info object
|
|
nsAutoCString host;
|
|
int32_t port = -1;
|
|
bool isHTTPS = false;
|
|
|
|
nsresult rv = mURI->SchemeIs("https", &isHTTPS);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
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;
|
|
|
|
LOG(("host=%s port=%d\n", host.get(), port));
|
|
|
|
rv = mURI->GetAsciiSpec(mSpec);
|
|
if (NS_FAILED(rv)) return rv;
|
|
LOG(("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);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsAutoCString type;
|
|
if (aProxyInfo && NS_SUCCEEDED(aProxyInfo->GetType(type)) &&
|
|
!type.EqualsLiteral("unknown"))
|
|
mProxyInfo = aProxyInfo;
|
|
|
|
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(nsIEncodedChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
|
|
NS_INTERFACE_MAP_ENTRY(nsIForcePendingChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIFormPOSTActionChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
|
|
NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIPrivateBrowsingChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsITimedChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIConsoleReportCollector)
|
|
NS_INTERFACE_MAP_ENTRY(nsIThrottledInputChannel)
|
|
NS_INTERFACE_MAP_ENTRY(nsIClassifiedChannel)
|
|
NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpBaseChannel)
|
|
NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIRequest
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetName(nsACString& aName)
|
|
{
|
|
aName = mSpec;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::IsPending(bool *aIsPending)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aIsPending);
|
|
*aIsPending = mIsPending || mForcePending;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetStatus(nsresult *aStatus)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aStatus);
|
|
*aStatus = mStatus;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aLoadGroup);
|
|
*aLoadGroup = mLoadGroup;
|
|
NS_IF_ADDREF(*aLoadGroup);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
|
|
|
|
if (!CanSetLoadGroup(aLoadGroup)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mLoadGroup = aLoadGroup;
|
|
mProgressSink = nullptr;
|
|
UpdatePrivateBrowsing();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aLoadFlags);
|
|
*aLoadFlags = mLoadFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
|
|
{
|
|
mLoadFlags = aLoadFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetDocshellUserAgentOverride()
|
|
{
|
|
// This sets the docshell specific user agent override, it will be overwritten
|
|
// by UserAgentOverrides.jsm if site-specific user agent overrides are set.
|
|
nsresult rv;
|
|
nsCOMPtr<nsILoadContext> loadContext;
|
|
NS_QueryNotificationCallbacks(this, loadContext);
|
|
if (!loadContext) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<mozIDOMWindowProxy> domWindow;
|
|
loadContext->GetAssociatedWindow(getter_AddRefs(domWindow));
|
|
if (!domWindow) {
|
|
return NS_OK;
|
|
}
|
|
|
|
auto* pDomWindow = nsPIDOMWindowOuter::From(domWindow);
|
|
nsIDocShell* docshell = pDomWindow->GetDocShell();
|
|
if (!docshell) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsString customUserAgent;
|
|
docshell->GetCustomUserAgent(customUserAgent);
|
|
if (customUserAgent.IsEmpty()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_ConvertUTF16toUTF8 utf8CustomUserAgent(customUserAgent);
|
|
rv = SetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), 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 = mOriginalURI;
|
|
NS_ADDREF(*aOriginalURI);
|
|
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 = mURI;
|
|
NS_ADDREF(*aURI);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetOwner(nsISupports **aOwner)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aOwner);
|
|
*aOwner = mOwner;
|
|
NS_IF_ADDREF(*aOwner);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetOwner(nsISupports *aOwner)
|
|
{
|
|
mOwner = aOwner;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetLoadInfo(nsILoadInfo *aLoadInfo)
|
|
{
|
|
mLoadInfo = aLoadInfo;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLoadInfo(nsILoadInfo **aLoadInfo)
|
|
{
|
|
NS_IF_ADDREF(*aLoadInfo = mLoadInfo);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetIsDocument(bool *aIsDocument)
|
|
{
|
|
return NS_GetIsDocumentChannel(this, aIsDocument);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks)
|
|
{
|
|
*aCallbacks = mCallbacks;
|
|
NS_IF_ADDREF(*aCallbacks);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
|
|
|
|
if (!CanSetCallbacks(aCallbacks)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mCallbacks = aCallbacks;
|
|
mProgressSink = nullptr;
|
|
|
|
UpdatePrivateBrowsing();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetContentType(nsACString& aContentType)
|
|
{
|
|
if (!mResponseHead) {
|
|
aContentType.Truncate();
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
mResponseHead->ContentType(aContentType);
|
|
if (!aContentType.IsEmpty()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
aContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetContentType(const nsACString& aContentType)
|
|
{
|
|
if (mListener || mWasOpened) {
|
|
if (!mResponseHead)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
nsAutoCString contentTypeBuf, charsetBuf;
|
|
bool hadCharset;
|
|
net_ParseContentType(aContentType, contentTypeBuf, charsetBuf, &hadCharset);
|
|
|
|
mResponseHead->SetContentType(contentTypeBuf);
|
|
|
|
// take care not to stomp on an existing charset
|
|
if (hadCharset)
|
|
mResponseHead->SetContentCharset(charsetBuf);
|
|
|
|
} else {
|
|
// We are being given a content-type hint.
|
|
bool dummy;
|
|
net_ParseContentType(aContentType, mContentTypeHint, mContentCharsetHint,
|
|
&dummy);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetContentCharset(nsACString& aContentCharset)
|
|
{
|
|
if (!mResponseHead)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
mResponseHead->ContentCharset(aContentCharset);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetContentCharset(const nsACString& aContentCharset)
|
|
{
|
|
if (mListener) {
|
|
if (!mResponseHead)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
mResponseHead->SetContentCharset(aContentCharset);
|
|
} else {
|
|
// Charset hint
|
|
mContentCharsetHint = aContentCharset;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetContentDisposition(uint32_t *aContentDisposition)
|
|
{
|
|
nsresult rv;
|
|
nsCString header;
|
|
|
|
rv = GetContentDispositionHeader(header);
|
|
if (NS_FAILED(rv)) {
|
|
if (mContentDispositionHint == UINT32_MAX)
|
|
return rv;
|
|
|
|
*aContentDisposition = mContentDispositionHint;
|
|
return NS_OK;
|
|
}
|
|
|
|
*aContentDisposition = NS_GetContentDispositionFromHeader(header, this);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetContentDisposition(uint32_t aContentDisposition)
|
|
{
|
|
mContentDispositionHint = aContentDisposition;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetContentDispositionFilename(nsAString& aContentDispositionFilename)
|
|
{
|
|
aContentDispositionFilename.Truncate();
|
|
nsresult rv;
|
|
nsCString header;
|
|
|
|
rv = GetContentDispositionHeader(header);
|
|
if (NS_FAILED(rv)) {
|
|
if (!mContentDispositionFilename)
|
|
return rv;
|
|
|
|
aContentDispositionFilename = *mContentDispositionFilename;
|
|
return NS_OK;
|
|
}
|
|
|
|
return NS_GetFilenameFromDisposition(aContentDispositionFilename,
|
|
header, mURI);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetContentDispositionFilename(const nsAString& aContentDispositionFilename)
|
|
{
|
|
mContentDispositionFilename = new nsString(aContentDispositionFilename);
|
|
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 (!mAvailableCachedAltDataType.IsEmpty()) {
|
|
*aContentLength = mAltDataLength;
|
|
return NS_OK;
|
|
}
|
|
|
|
*aContentLength = mResponseHead->ContentLength();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetContentLength(int64_t value)
|
|
{
|
|
MOZ_ASSERT_UNREACHABLE("HttpBaseChannel::SetContentLength");
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::Open(nsIInputStream **aResult)
|
|
{
|
|
NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_IN_PROGRESS);
|
|
|
|
if (!gHttpHandler->Active()) {
|
|
LOG(("HttpBaseChannel::Open after HTTP shutdown..."));
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
return NS_ImplementChannelOpen(this, aResult);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::Open2(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);
|
|
return Open(aStream);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIUploadChannel
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetUploadStream(nsIInputStream **stream)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(stream);
|
|
*stream = mUploadStream;
|
|
NS_IF_ADDREF(*stream);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetUploadStream(nsIInputStream *stream,
|
|
const nsACString &contentTypeArg,
|
|
int64_t contentLength)
|
|
{
|
|
// NOTE: for backwards compatibility and for compatibility with old style
|
|
// plugins, |stream| may include headers, specifically Content-Type and
|
|
// Content-Length headers. in this case, |contentType| and |contentLength|
|
|
// would be unspecified. this is traditionally the case of a POST request,
|
|
// and so we select POST as the request method if contentType and
|
|
// contentLength are unspecified.
|
|
|
|
if (stream) {
|
|
nsAutoCString method;
|
|
bool hasHeaders = false;
|
|
|
|
// This method and ExplicitSetUploadStream mean different things by "empty
|
|
// content type string". This method means "no header", but
|
|
// ExplicitSetUploadStream means "header with empty value". So we have to
|
|
// massage the contentType argument into the form ExplicitSetUploadStream
|
|
// expects.
|
|
nsCOMPtr<nsIMIMEInputStream> mimeStream;
|
|
nsCString contentType(contentTypeArg);
|
|
if (contentType.IsEmpty()) {
|
|
contentType.SetIsVoid(true);
|
|
method = NS_LITERAL_CSTRING("POST");
|
|
|
|
// 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 = NS_LITERAL_CSTRING("PUT");
|
|
|
|
MOZ_ASSERT(NS_FAILED(CallQueryInterface(stream, getter_AddRefs(mimeStream))),
|
|
"nsIMIMEInputStream should not be set with an explicit content type");
|
|
}
|
|
return ExplicitSetUploadStream(stream, contentType, contentLength,
|
|
method, hasHeaders);
|
|
}
|
|
|
|
// if stream is null, ExplicitSetUploadStream returns error.
|
|
// So we need special case for GET method.
|
|
mUploadStreamHasHeaders = false;
|
|
mRequestHead.SetMethod(NS_LITERAL_CSTRING("GET")); // revert to GET request
|
|
mUploadStream = stream;
|
|
return NS_OK;
|
|
}
|
|
|
|
namespace {
|
|
|
|
void
|
|
CopyComplete(void* aClosure, nsresult aStatus) {
|
|
#ifdef DEBUG
|
|
// Called on the STS thread by NS_AsyncCopy
|
|
nsCOMPtr<nsIEventTarget> sts =
|
|
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
|
|
bool result = false;
|
|
sts->IsOnCurrentThread(&result);
|
|
MOZ_ASSERT(result, "Should only be called on the STS thread.");
|
|
#endif
|
|
|
|
auto channel = static_cast<HttpBaseChannel*>(aClosure);
|
|
channel->OnCopyComplete(aStatus);
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::EnsureUploadStreamIsCloneable(nsIRunnable* aCallback)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
|
|
NS_ENSURE_ARG_POINTER(aCallback);
|
|
|
|
// We could in theory allow multiple callers to use this method,
|
|
// but the complexity does not seem worth it yet. Just fail if
|
|
// this is called more than once simultaneously.
|
|
NS_ENSURE_FALSE(mUploadCloneableCallback, NS_ERROR_UNEXPECTED);
|
|
|
|
// If the CloneUploadStream() will succeed, then synchronously invoke
|
|
// the callback to indicate we're already cloneable.
|
|
if (!mUploadStream || NS_InputStreamIsCloneable(mUploadStream)) {
|
|
aCallback->Run();
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIStorageStream> storageStream;
|
|
nsresult rv = NS_NewStorageStream(4096, UINT32_MAX,
|
|
getter_AddRefs(storageStream));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIInputStream> newUploadStream;
|
|
rv = storageStream->NewInputStream(0, getter_AddRefs(newUploadStream));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIOutputStream> sink;
|
|
rv = storageStream->GetOutputStream(0, getter_AddRefs(sink));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIInputStream> source;
|
|
if (NS_InputStreamIsBuffered(mUploadStream)) {
|
|
source = mUploadStream;
|
|
} else {
|
|
rv = NS_NewBufferedInputStream(getter_AddRefs(source),
|
|
mUploadStream.forget(), 4096);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
nsCOMPtr<nsIEventTarget> target =
|
|
do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
|
|
|
|
mUploadCloneableCallback = aCallback;
|
|
|
|
rv = NS_AsyncCopy(source, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS,
|
|
4096, // copy segment size
|
|
CopyComplete, this);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
mUploadCloneableCallback = nullptr;
|
|
return rv;
|
|
}
|
|
|
|
// Since we're consuming the old stream, replace it with the new
|
|
// stream immediately.
|
|
mUploadStream = newUploadStream;
|
|
|
|
// Explicity hold the stream alive until copying is complete. This will
|
|
// be released in EnsureUploadStreamIsCloneableComplete().
|
|
AddRef();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::OnCopyComplete(nsresult aStatus)
|
|
{
|
|
// Assert in parent process because we don't have to label the runnable
|
|
// in parent process.
|
|
MOZ_ASSERT(XRE_IsParentProcess());
|
|
|
|
nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<nsresult>(
|
|
"net::HttpBaseChannel::EnsureUploadStreamIsCloneableComplete",
|
|
this,
|
|
&HttpBaseChannel::EnsureUploadStreamIsCloneableComplete,
|
|
aStatus);
|
|
NS_DispatchToMainThread(runnable.forget());
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::EnsureUploadStreamIsCloneableComplete(nsresult aStatus)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
|
|
MOZ_ASSERT(mUploadCloneableCallback);
|
|
|
|
if (NS_SUCCEEDED(mStatus)) {
|
|
mStatus = aStatus;
|
|
}
|
|
|
|
mUploadCloneableCallback->Run();
|
|
mUploadCloneableCallback = nullptr;
|
|
|
|
// Release the reference we grabbed in EnsureUploadStreamIsCloneable() now
|
|
// that the copying is complete.
|
|
Release();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::CloneUploadStream(int64_t* aContentLength,
|
|
nsIInputStream** aClonedStream)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aContentLength);
|
|
NS_ENSURE_ARG_POINTER(aClonedStream);
|
|
*aClonedStream = nullptr;
|
|
|
|
if (!mUploadStream) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIInputStream> clonedStream;
|
|
nsresult rv = NS_CloneInputStream(mUploadStream, getter_AddRefs(clonedStream));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
clonedStream.forget(aClonedStream);
|
|
|
|
*aContentLength = mReqContentLength;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIUploadChannel2
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::ExplicitSetUploadStream(nsIInputStream *aStream,
|
|
const nsACString &aContentType,
|
|
int64_t aContentLength,
|
|
const nsACString &aMethod,
|
|
bool aStreamHasHeaders)
|
|
{
|
|
// Ensure stream is set and method is valid
|
|
NS_ENSURE_TRUE(aStream, NS_ERROR_FAILURE);
|
|
|
|
{
|
|
DebugOnly<nsCOMPtr<nsIMIMEInputStream>> mimeStream;
|
|
MOZ_ASSERT(!aStreamHasHeaders ||
|
|
NS_FAILED(CallQueryInterface(aStream, getter_AddRefs(mimeStream.value))),
|
|
"nsIMIMEInputStream should not include headers");
|
|
}
|
|
|
|
nsresult rv = SetRequestMethod(aMethod);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!aStreamHasHeaders && !aContentType.IsVoid()) {
|
|
if (aContentType.IsEmpty()) {
|
|
SetEmptyRequestHeader(NS_LITERAL_CSTRING("Content-Type"));
|
|
} else {
|
|
SetRequestHeader(NS_LITERAL_CSTRING("Content-Type"), aContentType,
|
|
false);
|
|
}
|
|
}
|
|
|
|
mUploadStreamHasHeaders = aStreamHasHeaders;
|
|
|
|
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aStream);
|
|
if (!seekable) {
|
|
nsCOMPtr<nsIInputStream> stream = aStream;
|
|
seekable = new PartiallySeekableInputStream(stream.forget());
|
|
}
|
|
|
|
mUploadStream = do_QueryInterface(seekable);
|
|
|
|
if (aContentLength >= 0) {
|
|
ExplicitSetUploadStreamLength(aContentLength, aStreamHasHeaders);
|
|
return NS_OK;
|
|
}
|
|
|
|
// Sync access to the stream length.
|
|
int64_t length;
|
|
if (InputStreamLengthHelper::GetSyncLength(aStream, &length)) {
|
|
ExplicitSetUploadStreamLength(length >= 0 ? length : 0,
|
|
aStreamHasHeaders);
|
|
return NS_OK;
|
|
}
|
|
|
|
// Let's resolve the size of the stream.
|
|
RefPtr<HttpBaseChannel> self = this;
|
|
InputStreamLengthHelper::GetAsyncLength(aStream,
|
|
[self, aStreamHasHeaders](int64_t aLength) {
|
|
self->mPendingInputStreamLengthOperation = false;
|
|
self->ExplicitSetUploadStreamLength(aLength >= 0 ? aLength : 0,
|
|
aStreamHasHeaders);
|
|
self->MaybeResumeAsyncOpen();
|
|
});
|
|
mPendingInputStreamLengthOperation = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
HttpBaseChannel::ExplicitSetUploadStreamLength(uint64_t aContentLength,
|
|
bool aStreamHasHeaders)
|
|
{
|
|
// We already have the content length. We don't need to determinate it.
|
|
mReqContentLength = aContentLength;
|
|
|
|
if (aStreamHasHeaders) {
|
|
return NS_OK;
|
|
}
|
|
|
|
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 NS_OK;
|
|
}
|
|
|
|
// SetRequestHeader propagates headers to chrome if HttpChannelChild
|
|
MOZ_ASSERT(!mWasOpened);
|
|
nsAutoCString contentLengthStr;
|
|
contentLengthStr.AppendInt(aContentLength);
|
|
SetRequestHeader(header, contentLengthStr, false);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetUploadStreamHasHeaders(bool *hasHeaders)
|
|
{
|
|
NS_ENSURE_ARG(hasHeaders);
|
|
|
|
*hasHeaders = mUploadStreamHasHeaders;
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
HttpBaseChannel::MaybeWaitForUploadStreamLength(nsIStreamListener *aListener,
|
|
nsISupports *aContext)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(!mAsyncOpenWaitingForStreamLength, "AsyncOpen() called twice?");
|
|
|
|
if (!mPendingInputStreamLengthOperation) {
|
|
return false;
|
|
}
|
|
|
|
mListener = aListener;
|
|
mListenerContext = aContext;
|
|
mAsyncOpenWaitingForStreamLength = true;
|
|
return true;
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::MaybeResumeAsyncOpen()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(!mPendingInputStreamLengthOperation);
|
|
|
|
if (!mAsyncOpenWaitingForStreamLength) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIStreamListener> listener;
|
|
listener.swap(mListener);
|
|
|
|
nsCOMPtr<nsISupports> context;
|
|
context.swap(mListenerContext);
|
|
|
|
mAsyncOpenWaitingForStreamLength = false;
|
|
|
|
nsresult rv = AsyncOpen(listener, context);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
DoAsyncAbort(rv);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIEncodedChannel
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetApplyConversion(bool *value)
|
|
{
|
|
*value = mApplyConversion;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetApplyConversion(bool value)
|
|
{
|
|
LOG(("HttpBaseChannel::SetApplyConversion [this=%p value=%d]\n", this, value));
|
|
mApplyConversion = value;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener,
|
|
nsIStreamListener** aNewNextListener)
|
|
{
|
|
return DoApplyContentConversions(aNextListener,
|
|
aNewNextListener,
|
|
mListenerContext);
|
|
}
|
|
|
|
// 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, nsISupports *aContext) override
|
|
{
|
|
return mNext->OnStartRequest(aRequest, aContext);
|
|
}
|
|
|
|
NS_IMETHOD OnStopRequest(nsIRequest *aRequest, nsISupports *aContext, 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, aContext, aStatusCode);
|
|
}
|
|
|
|
NS_IMETHOD OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext,
|
|
nsIInputStream *aInputStream, uint64_t aOffset,
|
|
uint32_t aCount) override
|
|
{
|
|
return mNext->OnDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount);
|
|
}
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(InterceptFailedOnStop, nsIStreamListener, nsIRequestObserver)
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener,
|
|
nsIStreamListener** aNewNextListener,
|
|
nsISupports *aCtxt)
|
|
{
|
|
*aNewNextListener = nullptr;
|
|
if (!mResponseHead || ! aNextListener) {
|
|
return NS_OK;
|
|
}
|
|
|
|
LOG(("HttpBaseChannel::DoApplyContentConversions [this=%p]\n", this));
|
|
|
|
if (!mApplyConversion) {
|
|
LOG(("not applying conversion per mApplyConversion\n"));
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!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;
|
|
}
|
|
|
|
bool isHTTPS = false;
|
|
mURI->SchemeIs("https", &isHTTPS);
|
|
if (gHttpHandler->IsAcceptableEncoding(val, isHTTPS)) {
|
|
nsCOMPtr<nsIStreamConverterService> serv;
|
|
rv = gHttpHandler->GetStreamConverterService(getter_AddRefs(serv));
|
|
|
|
// we won't fail to load the page just because we couldn't load the
|
|
// stream converter service.. carry on..
|
|
if (NS_FAILED(rv)) {
|
|
if (val)
|
|
LOG(("Unknown content encoding '%s', ignoring\n", val));
|
|
continue;
|
|
}
|
|
|
|
nsCOMPtr<nsIStreamListener> converter;
|
|
nsAutoCString from(val);
|
|
ToLowerCase(from);
|
|
rv = serv->AsyncConvertData(from.get(),
|
|
"uncompressed",
|
|
nextListener,
|
|
aCtxt,
|
|
getter_AddRefs(converter));
|
|
if (NS_FAILED(rv)) {
|
|
LOG(("Unexpected failure of AsyncConvertData %s\n", val));
|
|
return rv;
|
|
}
|
|
|
|
LOG(("converter removed '%s' content-encoding\n", val));
|
|
if (gHttpHandler->IsTelemetryEnabled()) {
|
|
int mode = 0;
|
|
if (from.EqualsLiteral("gzip") || from.EqualsLiteral("x-gzip")) {
|
|
mode = 1;
|
|
} else if (from.EqualsLiteral("deflate") || from.EqualsLiteral("x-deflate")) {
|
|
mode = 2;
|
|
} else if (from.EqualsLiteral("br")) {
|
|
mode = 3;
|
|
}
|
|
Telemetry::Accumulate(Telemetry::HTTP_CONTENT_ENCODING, mode);
|
|
}
|
|
nextListener = converter;
|
|
}
|
|
else {
|
|
if (val)
|
|
LOG(("Unknown content encoding '%s', ignoring\n", val));
|
|
}
|
|
}
|
|
*aNewNextListener = nextListener;
|
|
NS_IF_ADDREF(*aNewNextListener);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetContentEncodings(nsIUTF8StringEnumerator** aEncodings)
|
|
{
|
|
if (!mResponseHead) {
|
|
*aEncodings = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsAutoCString encoding;
|
|
Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, encoding);
|
|
if (encoding.IsEmpty()) {
|
|
*aEncodings = nullptr;
|
|
return NS_OK;
|
|
}
|
|
nsContentEncodings* enumerator = new nsContentEncodings(this,
|
|
encoding.get());
|
|
NS_ADDREF(*aEncodings = enumerator);
|
|
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(NS_LITERAL_CSTRING("gzip"), start, end)) {
|
|
aNextEncoding.AssignLiteral(APPLICATION_GZIP);
|
|
haveType = true;
|
|
}
|
|
|
|
if (!haveType) {
|
|
encoding.BeginReading(start);
|
|
if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("compress"), start, end)) {
|
|
aNextEncoding.AssignLiteral(APPLICATION_COMPRESS);
|
|
haveType = true;
|
|
}
|
|
}
|
|
|
|
if (!haveType) {
|
|
encoding.BeginReading(start);
|
|
if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("deflate"), start, end)) {
|
|
aNextEncoding.AssignLiteral(APPLICATION_ZIP);
|
|
haveType = true;
|
|
}
|
|
}
|
|
|
|
if (!haveType) {
|
|
encoding.BeginReading(start);
|
|
if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("br"), start, end)) {
|
|
aNextEncoding.AssignLiteral(APPLICATION_BROTLI);
|
|
haveType = true;
|
|
}
|
|
}
|
|
|
|
// Prepare to fetch the next encoding
|
|
mCurEnd = mCurStart;
|
|
mReady = false;
|
|
|
|
if (haveType)
|
|
return NS_OK;
|
|
|
|
NS_WARNING("Unknown encoding type");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsContentEncodings::nsISupports
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMPL_ISUPPORTS(HttpBaseChannel::nsContentEncodings, nsIUTF8StringEnumerator)
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsContentEncodings <private>
|
|
//-----------------------------------------------------------------------------
|
|
|
|
nsresult
|
|
HttpBaseChannel::nsContentEncodings::PrepareForNext(void)
|
|
{
|
|
MOZ_ASSERT(mCurStart == mCurEnd, "Indeterminate state");
|
|
|
|
// At this point both mCurStart and mCurEnd point to somewhere
|
|
// past the end of the next thing we want to return
|
|
|
|
while (mCurEnd != mEncodingHeader) {
|
|
--mCurEnd;
|
|
if (*mCurEnd != ',' && !nsCRT::IsAsciiSpace(*mCurEnd))
|
|
break;
|
|
}
|
|
if (mCurEnd == mEncodingHeader)
|
|
return NS_ERROR_NOT_AVAILABLE; // no more encodings
|
|
++mCurEnd;
|
|
|
|
// At this point mCurEnd points to the first char _after_ the
|
|
// header we want. Furthermore, mCurEnd - 1 != mEncodingHeader
|
|
|
|
mCurStart = mCurEnd - 1;
|
|
while (mCurStart != mEncodingHeader &&
|
|
*mCurStart != ',' && !nsCRT::IsAsciiSpace(*mCurStart))
|
|
--mCurStart;
|
|
if (*mCurStart == ',' || nsCRT::IsAsciiSpace(*mCurStart))
|
|
++mCurStart; // we stopped because of a weird char, so move up one
|
|
|
|
// At this point mCurStart and mCurEnd bracket the encoding string
|
|
// we want. Check that it's not "identity"
|
|
if (Substring(mCurStart, mCurEnd).Equals("identity",
|
|
nsCaseInsensitiveCStringComparator())) {
|
|
mCurEnd = mCurStart;
|
|
return PrepareForNext();
|
|
}
|
|
|
|
mReady = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIHttpChannel
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetChannelId(uint64_t *aChannelId)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aChannelId);
|
|
*aChannelId = mChannelId;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetChannelId(uint64_t aChannelId)
|
|
{
|
|
mChannelId = aChannelId;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP HttpBaseChannel::GetTopLevelContentWindowId(uint64_t *aWindowId)
|
|
{
|
|
if (!mContentWindowId) {
|
|
nsCOMPtr<nsILoadContext> loadContext;
|
|
GetCallback(loadContext);
|
|
if (loadContext) {
|
|
nsCOMPtr<mozIDOMWindowProxy> topWindow;
|
|
loadContext->GetTopWindow(getter_AddRefs(topWindow));
|
|
nsCOMPtr<nsIDOMWindowUtils> windowUtils;
|
|
if (topWindow) {
|
|
windowUtils = nsGlobalWindowOuter::Cast(topWindow)->WindowUtils();
|
|
}
|
|
if (windowUtils) {
|
|
windowUtils->GetCurrentInnerWindowID(&mContentWindowId);
|
|
}
|
|
}
|
|
}
|
|
*aWindowId = mContentWindowId;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP HttpBaseChannel::SetTopLevelOuterContentWindowId(uint64_t aWindowId)
|
|
{
|
|
mTopLevelOuterContentWindowId = aWindowId;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP HttpBaseChannel::GetTopLevelOuterContentWindowId(uint64_t *aWindowId)
|
|
{
|
|
EnsureTopLevelOuterContentWindowId();
|
|
*aWindowId = mTopLevelOuterContentWindowId;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP HttpBaseChannel::SetTopLevelContentWindowId(uint64_t aWindowId)
|
|
{
|
|
mContentWindowId = aWindowId;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetIsTrackingResource(bool* aIsTrackingResource)
|
|
{
|
|
*aIsTrackingResource = mIsTrackingResource;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::OverrideTrackingResource(bool aIsTracking)
|
|
{
|
|
LOG(("HttpBaseChannel::OverrideTrackingResource(%d) %p "
|
|
"mIsTrackingResource=%d",
|
|
(int) aIsTracking, this, (int) mIsTrackingResource));
|
|
|
|
mIsTrackingResource = aIsTracking;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetTransferSize(uint64_t *aTransferSize)
|
|
{
|
|
*aTransferSize = mTransferSize;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetDecodedBodySize(uint64_t *aDecodedBodySize)
|
|
{
|
|
*aDecodedBodySize = mDecodedBodySize;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetEncodedBodySize(uint64_t *aEncodedBodySize)
|
|
{
|
|
*aEncodedBodySize = mEncodedBodySize;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRequestMethod(nsACString& aMethod)
|
|
{
|
|
mRequestHead.Method(aMethod);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetRequestMethod(const nsACString& aMethod)
|
|
{
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
|
|
const nsCString& flatMethod = PromiseFlatCString(aMethod);
|
|
|
|
// Method names are restricted to valid HTTP tokens.
|
|
if (!nsHttp::IsValidToken(flatMethod))
|
|
return NS_ERROR_INVALID_ARG;
|
|
|
|
mRequestHead.SetMethod(flatMethod);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetReferrer(nsIURI **referrer)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(referrer);
|
|
*referrer = mReferrer;
|
|
NS_IF_ADDREF(*referrer);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetReferrer(nsIURI *referrer)
|
|
{
|
|
bool isPrivate = mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
|
|
return SetReferrerWithPolicy(referrer, NS_GetDefaultReferrerPolicy(isPrivate));
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetReferrerPolicy(uint32_t *referrerPolicy)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(referrerPolicy);
|
|
*referrerPolicy = mReferrerPolicy;
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
/* Computing whether our URI is cross-origin may be expensive, so please do
|
|
* that in cases where we're going to use this information later on.
|
|
*/
|
|
bool
|
|
HttpBaseChannel::IsCrossOriginWithReferrer()
|
|
{
|
|
nsresult rv;
|
|
nsCOMPtr<nsIURI> triggeringURI;
|
|
if (mLoadInfo) {
|
|
nsCOMPtr<nsIPrincipal> triggeringPrincipal = mLoadInfo->TriggeringPrincipal();
|
|
if (triggeringPrincipal) {
|
|
triggeringPrincipal->GetURI(getter_AddRefs(triggeringURI));
|
|
}
|
|
}
|
|
if (triggeringURI) {
|
|
if (LOG_ENABLED()) {
|
|
nsAutoCString triggeringURISpec;
|
|
triggeringURI->GetAsciiSpec(triggeringURISpec);
|
|
LOG(("triggeringURI=%s\n", triggeringURISpec.get()));
|
|
}
|
|
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
|
rv = ssm->CheckSameOriginURI(triggeringURI, mURI, false);
|
|
return (NS_FAILED(rv));
|
|
}
|
|
|
|
LOG(("no triggering principal available via loadInfo, assuming load is cross-origin"));
|
|
return true;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetReferrerWithPolicy(nsIURI *referrer,
|
|
uint32_t referrerPolicy)
|
|
{
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
|
|
mReferrerPolicy = referrerPolicy;
|
|
|
|
// clear existing referrer, if any
|
|
mReferrer = nullptr;
|
|
nsresult rv = mRequestHead.ClearHeader(nsHttp::Referer);
|
|
if(NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
if (mReferrerPolicy == REFERRER_POLICY_UNSET) {
|
|
bool isPrivate = mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
|
|
mReferrerPolicy = NS_GetDefaultReferrerPolicy(isPrivate);
|
|
}
|
|
|
|
if (!referrer) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Don't send referrer at all when the meta referrer setting is "no-referrer"
|
|
if (mReferrerPolicy == REFERRER_POLICY_NO_REFERRER) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// 0: never send referer
|
|
// 1: send referer for direct user action
|
|
// 2: always send referer
|
|
uint32_t userReferrerLevel = gHttpHandler->ReferrerLevel();
|
|
|
|
// false: use real referrer
|
|
// true: spoof with URI of the current request
|
|
bool userSpoofReferrerSource = gHttpHandler->SpoofReferrerSource();
|
|
|
|
// false: use real referrer when leaving .onion
|
|
// true: use an empty referrer
|
|
bool userHideOnionReferrerSource = gHttpHandler->HideOnionReferrerSource();
|
|
|
|
// 0: send referer no matter what
|
|
// 1: send referer ONLY when base domains match
|
|
// 2: send referer ONLY when hosts match
|
|
int userReferrerXOriginPolicy = gHttpHandler->ReferrerXOriginPolicy();
|
|
|
|
// check referrer blocking pref
|
|
uint32_t referrerLevel;
|
|
if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
|
|
referrerLevel = 1; // user action
|
|
} else {
|
|
referrerLevel = 2; // inline content
|
|
}
|
|
if (userReferrerLevel < referrerLevel) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> referrerGrip;
|
|
bool match;
|
|
|
|
//
|
|
// Strip off "wyciwyg://123/" from wyciwyg referrers.
|
|
//
|
|
// XXX this really belongs elsewhere since wyciwyg URLs aren't part of necko.
|
|
// perhaps some sort of generic nsINestedURI could be used. then, if an URI
|
|
// fails the whitelist test, then we could check for an inner URI and try
|
|
// that instead. though, that might be too automatic.
|
|
//
|
|
rv = referrer->SchemeIs("wyciwyg", &match);
|
|
if (NS_FAILED(rv)) return rv;
|
|
if (match) {
|
|
nsAutoCString path;
|
|
rv = referrer->GetPathQueryRef(path);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
uint32_t pathLength = path.Length();
|
|
if (pathLength <= 2) return NS_ERROR_FAILURE;
|
|
|
|
// Path is of the form "//123/http://foo/bar", with a variable number of
|
|
// digits. To figure out where the "real" URL starts, search path for a
|
|
// '/', starting at the third character.
|
|
int32_t slashIndex = path.FindChar('/', 2);
|
|
if (slashIndex == kNotFound) return NS_ERROR_FAILURE;
|
|
|
|
// Replace |referrer| with a URI without wyciwyg://123/.
|
|
rv = NS_NewURI(getter_AddRefs(referrerGrip),
|
|
Substring(path, slashIndex + 1, pathLength - slashIndex - 1));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
referrer = referrerGrip.get();
|
|
}
|
|
|
|
// Enforce Referrer whitelist
|
|
if (!IsReferrerSchemeAllowed(referrer)) {
|
|
return NS_OK; // kick out....
|
|
}
|
|
|
|
//
|
|
// Handle secure referrals.
|
|
//
|
|
// Support referrals from a secure server if this is a secure site
|
|
// and (optionally) if the host names are the same.
|
|
//
|
|
rv = referrer->SchemeIs("https", &match);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
if (match) {
|
|
rv = mURI->SchemeIs("https", &match);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// It's ok to send referrer for https-to-http scenarios if the referrer
|
|
// policy is "unsafe-url", "origin", or "origin-when-cross-origin".
|
|
if (mReferrerPolicy != REFERRER_POLICY_UNSAFE_URL &&
|
|
mReferrerPolicy != REFERRER_POLICY_ORIGIN_WHEN_XORIGIN &&
|
|
mReferrerPolicy != REFERRER_POLICY_ORIGIN) {
|
|
|
|
// in other referrer policies, https->http is not allowed...
|
|
if (!match) return NS_OK;
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> clone;
|
|
//
|
|
// we need to clone the referrer, so we can:
|
|
// (1) modify it
|
|
// (2) keep a reference to it after returning from this function
|
|
//
|
|
// Strip away any fragment per RFC 2616 section 14.36
|
|
// and Referrer Policy section 6.3.5.
|
|
rv = NS_GetURIWithoutRef(referrer, getter_AddRefs(clone));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsAutoCString currentHost;
|
|
nsAutoCString referrerHost;
|
|
|
|
rv = mURI->GetAsciiHost(currentHost);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = clone->GetAsciiHost(referrerHost);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Send an empty referrer if leaving a .onion domain.
|
|
if(userHideOnionReferrerSource &&
|
|
!currentHost.Equals(referrerHost) &&
|
|
StringEndsWith(referrerHost, NS_LITERAL_CSTRING(".onion"))) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// check policy for sending ref only when hosts match
|
|
if (userReferrerXOriginPolicy == 2 && !currentHost.Equals(referrerHost))
|
|
return NS_OK;
|
|
|
|
if (userReferrerXOriginPolicy == 1) {
|
|
nsAutoCString currentDomain = currentHost;
|
|
nsAutoCString referrerDomain = referrerHost;
|
|
uint32_t extraDomains = 0;
|
|
nsCOMPtr<nsIEffectiveTLDService> eTLDService = do_GetService(
|
|
NS_EFFECTIVETLDSERVICE_CONTRACTID);
|
|
if (eTLDService) {
|
|
rv = eTLDService->GetBaseDomain(mURI, extraDomains, currentDomain);
|
|
if (NS_FAILED(rv)) return rv;
|
|
rv = eTLDService->GetBaseDomain(clone, extraDomains, referrerDomain);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
// check policy for sending only when effective top level domain matches.
|
|
// this falls back on using host if eTLDService does not work
|
|
if (!currentDomain.Equals(referrerDomain))
|
|
return NS_OK;
|
|
}
|
|
|
|
// send spoofed referrer if desired
|
|
if (userSpoofReferrerSource) {
|
|
nsCOMPtr<nsIURI> mURIclone;
|
|
rv = NS_GetURIWithoutRef(mURI, getter_AddRefs(mURIclone));
|
|
if (NS_FAILED(rv)) return rv;
|
|
clone = mURIclone;
|
|
currentHost = referrerHost;
|
|
}
|
|
|
|
// strip away any userpass; we don't want to be giving out passwords ;-)
|
|
// This is required by Referrer Policy stripping algorithm.
|
|
rv = NS_MutateURI(clone)
|
|
.SetUserPass(EmptyCString())
|
|
.Finalize(clone);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// 0: full URI
|
|
// 1: scheme+host+port+path
|
|
// 2: scheme+host+port
|
|
int userReferrerTrimmingPolicy = gHttpHandler->ReferrerTrimmingPolicy();
|
|
int userReferrerXOriginTrimmingPolicy =
|
|
gHttpHandler->ReferrerXOriginTrimmingPolicy();
|
|
|
|
switch (mReferrerPolicy) {
|
|
case REFERRER_POLICY_SAME_ORIGIN:
|
|
// Don't send referrer when the request is cross-origin and policy is "same-origin".
|
|
if (IsCrossOriginWithReferrer()) {
|
|
return NS_OK;
|
|
}
|
|
break;
|
|
|
|
case REFERRER_POLICY_ORIGIN:
|
|
case REFERRER_POLICY_STRICT_ORIGIN:
|
|
userReferrerTrimmingPolicy = 2;
|
|
break;
|
|
|
|
case REFERRER_POLICY_ORIGIN_WHEN_XORIGIN:
|
|
case REFERRER_POLICY_STRICT_ORIGIN_WHEN_XORIGIN:
|
|
if (userReferrerTrimmingPolicy != 2 && IsCrossOriginWithReferrer()) {
|
|
// Ignore set userReferrerTrimmingPolicy if it is already the strictest
|
|
// policy.
|
|
userReferrerTrimmingPolicy = 2;
|
|
}
|
|
break;
|
|
|
|
case REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE:
|
|
case REFERRER_POLICY_UNSAFE_URL:
|
|
if (userReferrerTrimmingPolicy != 2) {
|
|
// Ignore set userReferrerTrimmingPolicy if it is already the strictest
|
|
// policy. Apply the user cross-origin trimming policy if it's more
|
|
// restrictive than the general one.
|
|
if (userReferrerXOriginTrimmingPolicy != 0 && IsCrossOriginWithReferrer()) {
|
|
userReferrerTrimmingPolicy =
|
|
std::max(userReferrerTrimmingPolicy, userReferrerXOriginTrimmingPolicy);
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case REFERRER_POLICY_NO_REFERRER:
|
|
case REFERRER_POLICY_UNSET:
|
|
default:
|
|
MOZ_ASSERT_UNREACHABLE("Unexpected value");
|
|
break;
|
|
}
|
|
|
|
nsAutoCString spec;
|
|
// check how much referer to send
|
|
if (userReferrerTrimmingPolicy) {
|
|
// All output strings start with: scheme+host+port
|
|
// We want the IDN-normalized PrePath. That's not something currently
|
|
// available and there doesn't yet seem to be justification for adding it to
|
|
// the interfaces, so just build it up ourselves from scheme+AsciiHostPort
|
|
nsAutoCString scheme, asciiHostPort;
|
|
rv = clone->GetScheme(scheme);
|
|
if (NS_FAILED(rv)) return rv;
|
|
spec = scheme;
|
|
spec.AppendLiteral("://");
|
|
// Note we explicitly cleared UserPass above, so do not need to build it.
|
|
rv = clone->GetAsciiHostPort(asciiHostPort);
|
|
if (NS_FAILED(rv)) return rv;
|
|
spec.Append(asciiHostPort);
|
|
|
|
switch (userReferrerTrimmingPolicy) {
|
|
case 1: { // scheme+host+port+path
|
|
nsCOMPtr<nsIURL> url(do_QueryInterface(clone));
|
|
if (url) {
|
|
nsAutoCString path;
|
|
rv = url->GetFilePath(path);
|
|
if (NS_FAILED(rv)) return rv;
|
|
spec.Append(path);
|
|
rv = NS_MutateURI(url)
|
|
.SetQuery(EmptyCString())
|
|
.SetRef(EmptyCString())
|
|
.Finalize(clone);
|
|
if (NS_FAILED(rv)) return rv;
|
|
break;
|
|
}
|
|
// No URL, so fall through to truncating the path and any query/ref off
|
|
// as well.
|
|
}
|
|
MOZ_FALLTHROUGH;
|
|
default: // (Pref limited to [0,2] enforced by clamp, MOZ_CRASH overkill.)
|
|
case 2: // scheme+host+port+/
|
|
spec.AppendLiteral("/");
|
|
// This nukes any query/ref present as well in the case of nsStandardURL
|
|
rv = NS_MutateURI(clone)
|
|
.SetPathQueryRef(EmptyCString())
|
|
.Finalize(clone);
|
|
if (NS_FAILED(rv)) return rv;
|
|
break;
|
|
}
|
|
} else {
|
|
// use the full URI
|
|
rv = clone->GetAsciiSpec(spec);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
// finally, remember the referrer URI and set the Referer header.
|
|
rv = SetRequestHeader(NS_LITERAL_CSTRING("Referer"), spec, false);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
mReferrer = clone;
|
|
return NS_OK;
|
|
}
|
|
|
|
// Return the channel's proxy URI, or if it doesn't exist, the
|
|
// channel's main URI.
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetProxyURI(nsIURI **aOut)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aOut);
|
|
nsCOMPtr<nsIURI> result(mProxyURI);
|
|
result.forget(aOut);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRequestHeader(const nsACString& aHeader,
|
|
nsACString& aValue)
|
|
{
|
|
aValue.Truncate();
|
|
|
|
// XXX might be better to search the header list directly instead of
|
|
// hitting the http atom hash table.
|
|
nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
|
|
if (!atom)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
return mRequestHead.GetHeader(atom, aValue);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetRequestHeader(const nsACString& aHeader,
|
|
const nsACString& aValue,
|
|
bool aMerge)
|
|
{
|
|
const nsCString &flatHeader = PromiseFlatCString(aHeader);
|
|
const nsCString &flatValue = PromiseFlatCString(aValue);
|
|
|
|
LOG(("HttpBaseChannel::SetRequestHeader [this=%p header=\"%s\" value=\"%s\" merge=%u]\n",
|
|
this, flatHeader.get(), flatValue.get(), aMerge));
|
|
|
|
// Verify header names are valid HTTP tokens and header values are reasonably
|
|
// close to whats allowed in RFC 2616.
|
|
if (!nsHttp::IsValidToken(flatHeader) ||
|
|
!nsHttp::IsReasonableHeaderValue(flatValue)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
return mRequestHead.SetHeader(aHeader, flatValue, aMerge);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetEmptyRequestHeader(const nsACString& aHeader)
|
|
{
|
|
const nsCString &flatHeader = PromiseFlatCString(aHeader);
|
|
|
|
LOG(("HttpBaseChannel::SetEmptyRequestHeader [this=%p header=\"%s\"]\n",
|
|
this, flatHeader.get()));
|
|
|
|
// Verify header names are valid HTTP tokens and header values are reasonably
|
|
// close to whats allowed in RFC 2616.
|
|
if (!nsHttp::IsValidToken(flatHeader)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
return mRequestHead.SetEmptyHeader(aHeader);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::VisitRequestHeaders(nsIHttpHeaderVisitor *visitor)
|
|
{
|
|
return mRequestHead.VisitHeaders(visitor);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::VisitNonDefaultRequestHeaders(nsIHttpHeaderVisitor *visitor)
|
|
{
|
|
return mRequestHead.VisitHeaders(visitor,
|
|
nsHttpHeaderArray::eFilterSkipDefault);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetResponseHeader(const nsACString &header, nsACString &value)
|
|
{
|
|
value.Truncate();
|
|
|
|
if (!mResponseHead)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
nsHttpAtom atom = nsHttp::ResolveAtom(header);
|
|
if (!atom)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
return mResponseHead->GetHeader(atom, value);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetResponseHeader(const nsACString& header,
|
|
const nsACString& value,
|
|
bool merge)
|
|
{
|
|
LOG(("HttpBaseChannel::SetResponseHeader [this=%p header=\"%s\" value=\"%s\" merge=%u]\n",
|
|
this, PromiseFlatCString(header).get(), PromiseFlatCString(value).get(), merge));
|
|
|
|
if (!mResponseHead)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
nsHttpAtom atom = nsHttp::ResolveAtom(header);
|
|
if (!atom)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
// these response headers must not be changed
|
|
if (atom == nsHttp::Content_Type ||
|
|
atom == nsHttp::Content_Length ||
|
|
atom == nsHttp::Content_Encoding ||
|
|
atom == nsHttp::Trailer ||
|
|
atom == nsHttp::Transfer_Encoding)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
mResponseHeadersModified = true;
|
|
|
|
return mResponseHead->SetHeader(header, value, merge);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::VisitResponseHeaders(nsIHttpHeaderVisitor *visitor)
|
|
{
|
|
if (!mResponseHead) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
return mResponseHead->VisitHeaders(visitor,
|
|
nsHttpHeaderArray::eFilterResponse);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetOriginalResponseHeader(const nsACString& aHeader,
|
|
nsIHttpHeaderVisitor *aVisitor)
|
|
{
|
|
if (!mResponseHead) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
|
|
if (!atom) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
return mResponseHead->GetOriginalHeader(atom, aVisitor);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::VisitOriginalResponseHeaders(nsIHttpHeaderVisitor *aVisitor)
|
|
{
|
|
if (!mResponseHead) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
return mResponseHead->VisitHeaders(aVisitor,
|
|
nsHttpHeaderArray::eFilterResponseOriginal);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetAllowPipelining(bool *value)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(value);
|
|
*value = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetAllowPipelining(bool value)
|
|
{
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
// nop
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetAllowSTS(bool *value)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(value);
|
|
*value = mAllowSTS;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetAllowSTS(bool value)
|
|
{
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
|
|
mAllowSTS = value;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRedirectionLimit(uint32_t *value)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(value);
|
|
*value = mRedirectionLimit;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetRedirectionLimit(uint32_t value)
|
|
{
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
|
|
mRedirectionLimit = std::min<uint32_t>(value, 0xff);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
HttpBaseChannel::OverrideSecurityInfo(nsISupports* aSecurityInfo)
|
|
{
|
|
MOZ_ASSERT(!mSecurityInfo,
|
|
"This can only be called when we don't have a security info object already");
|
|
MOZ_RELEASE_ASSERT(aSecurityInfo,
|
|
"This can only be called with a valid security info object");
|
|
MOZ_ASSERT(!BypassServiceWorker(),
|
|
"This can only be called on channels that are not bypassing interception");
|
|
MOZ_ASSERT(mResponseCouldBeSynthesized,
|
|
"This can only be called on channels that can be intercepted");
|
|
if (mSecurityInfo) {
|
|
LOG(("HttpBaseChannel::OverrideSecurityInfo mSecurityInfo is null! "
|
|
"[this=%p]\n", this));
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
if (!mResponseCouldBeSynthesized) {
|
|
LOG(("HttpBaseChannel::OverrideSecurityInfo channel cannot be intercepted! "
|
|
"[this=%p]\n", this));
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
mSecurityInfo = aSecurityInfo;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::IsNoStoreResponse(bool *value)
|
|
{
|
|
if (!mResponseHead)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
*value = mResponseHead->NoStore();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::IsNoCacheResponse(bool *value)
|
|
{
|
|
if (!mResponseHead)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
*value = mResponseHead->NoCache();
|
|
if (!*value)
|
|
*value = mResponseHead->ExpiresInPast();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::IsPrivateResponse(bool *value)
|
|
{
|
|
if (!mResponseHead)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
*value = mResponseHead->Private();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetResponseStatus(uint32_t *aValue)
|
|
{
|
|
if (!mResponseHead)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
*aValue = mResponseHead->Status();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetResponseStatusText(nsACString& aValue)
|
|
{
|
|
if (!mResponseHead)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
mResponseHead->StatusText(aValue);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRequestSucceeded(bool *aValue)
|
|
{
|
|
if (!mResponseHead)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
uint32_t status = mResponseHead->Status();
|
|
*aValue = (status / 100 == 2);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::RedirectTo(nsIURI *targetURI)
|
|
{
|
|
NS_ENSURE_ARG(targetURI);
|
|
|
|
nsAutoCString spec;
|
|
targetURI->GetAsciiSpec(spec);
|
|
LOG(("HttpBaseChannel::RedirectTo [this=%p, uri=%s]", this, spec.get()));
|
|
LogCallingScriptLocation(this);
|
|
|
|
// We cannot redirect after OnStartRequest of the listener
|
|
// has been called, since to redirect we have to switch channels
|
|
// and the dance with OnStartRequest et al has to start over.
|
|
// This would break the nsIStreamListener contract.
|
|
NS_ENSURE_FALSE(mOnStartRequestCalled, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
mAPIRedirectToURI = targetURI;
|
|
// Only Web Extensions are allowed to redirect a channel to a data:
|
|
// URI. To avoid any bypasses after the channel was flagged by
|
|
// the WebRequst API, we are dropping the flag here.
|
|
if (mLoadInfo) {
|
|
mLoadInfo->SetAllowInsecureRedirectToDataURI(false);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::UpgradeToSecure()
|
|
{
|
|
// Upgrades are handled internally between http-on-modify-request and
|
|
// http-on-before-connect, which means upgrades are only possible during
|
|
// on-modify, or WebRequest.onBeforeRequest in Web Extensions. Once we are
|
|
// past the code path where upgrades are handled, attempting an upgrade
|
|
// will throw an error.
|
|
NS_ENSURE_TRUE(mUpgradableToSecure, NS_ERROR_NOT_AVAILABLE);
|
|
|
|
mUpgradeToSecure = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRequestContextID(uint64_t *aRCID)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aRCID);
|
|
*aRCID = mRequestContextID;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetRequestContextID(uint64_t aRCID)
|
|
{
|
|
mRequestContextID = aRCID;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetIsMainDocumentChannel(bool* aValue)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aValue);
|
|
*aValue = IsNavigation();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetIsMainDocumentChannel(bool aValue)
|
|
{
|
|
mForceMainDocumentChannel = aValue;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetProtocolVersion(nsACString& aProtocolVersion)
|
|
{
|
|
nsresult rv;
|
|
nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(mSecurityInfo, &rv);
|
|
nsAutoCString protocol;
|
|
if (NS_SUCCEEDED(rv) && ssl &&
|
|
NS_SUCCEEDED(ssl->GetNegotiatedNPN(protocol)) &&
|
|
!protocol.IsEmpty()) {
|
|
// The negotiated protocol was not empty so we can use it.
|
|
aProtocolVersion = protocol;
|
|
return NS_OK;
|
|
}
|
|
|
|
if (mResponseHead) {
|
|
HttpVersion version = mResponseHead->Version();
|
|
aProtocolVersion.Assign(nsHttp::GetProtocolVersion(version));
|
|
return NS_OK;
|
|
}
|
|
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIHttpChannelInternal
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetTopWindowURIIfUnknown(nsIURI *aTopWindowURI)
|
|
{
|
|
if (!aTopWindowURI) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
if (mTopWindowURI) {
|
|
LOG(("HttpChannelBase::SetTopWindowURIIfUnknown [this=%p] "
|
|
"mTopWindowURI is already set.\n", this));
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> topWindowURI;
|
|
Unused << GetTopWindowURI(getter_AddRefs(topWindowURI));
|
|
|
|
// Don't modify |mTopWindowURI| if we can get one from GetTopWindowURI().
|
|
if (topWindowURI) {
|
|
LOG(("HttpChannelBase::SetTopWindowURIIfUnknown [this=%p] "
|
|
"Return an error since we got a top window uri.\n", this));
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mTopWindowURI = aTopWindowURI;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetTopWindowURI(nsIURI **aTopWindowURI)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
nsCOMPtr<mozIThirdPartyUtil> util;
|
|
// Only compute the top window URI once. In e10s, this must be computed in the
|
|
// child. The parent gets the top window URI through HttpChannelOpenArgs.
|
|
if (!mTopWindowURI) {
|
|
util = services::GetThirdPartyUtil();
|
|
if (!util) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
nsCOMPtr<mozIDOMWindowProxy> win;
|
|
rv = util->GetTopWindowForChannel(this, getter_AddRefs(win));
|
|
if (NS_SUCCEEDED(rv)) {
|
|
rv = util->GetURIFromWindow(win, getter_AddRefs(mTopWindowURI));
|
|
#if DEBUG
|
|
if (mTopWindowURI) {
|
|
nsCString spec;
|
|
if (NS_SUCCEEDED(mTopWindowURI->GetSpec(spec))) {
|
|
LOG(("HttpChannelBase::Setting topwindow URI spec %s [this=%p]\n",
|
|
spec.get(), this));
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
NS_IF_ADDREF(*aTopWindowURI = mTopWindowURI);
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetDocumentURI(nsIURI **aDocumentURI)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aDocumentURI);
|
|
*aDocumentURI = mDocumentURI;
|
|
NS_IF_ADDREF(*aDocumentURI);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetDocumentURI(nsIURI *aDocumentURI)
|
|
{
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
|
|
mDocumentURI = aDocumentURI;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRequestVersion(uint32_t *major, uint32_t *minor)
|
|
{
|
|
HttpVersion version = mRequestHead.Version();
|
|
|
|
if (major) { *major = static_cast<uint32_t>(version) / 10; }
|
|
if (minor) { *minor = static_cast<uint32_t>(version) % 10; }
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetResponseVersion(uint32_t *major, uint32_t *minor)
|
|
{
|
|
if (!mResponseHead)
|
|
{
|
|
*major = *minor = 0; // we should at least be kind about it
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
HttpVersion version = mResponseHead->Version();
|
|
|
|
if (major) { *major = static_cast<uint32_t>(version) / 10; }
|
|
if (minor) { *minor = static_cast<uint32_t>(version) % 10; }
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::NotifySetCookie(char const *aCookie)
|
|
{
|
|
nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
|
|
if (obs) {
|
|
nsAutoString cookie;
|
|
CopyASCIItoUTF16(mozilla::MakeStringSpan(aCookie), cookie);
|
|
obs->NotifyObservers(static_cast<nsIChannel*>(this),
|
|
"http-on-response-set-cookie",
|
|
cookie.get());
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetCookie(const char *aCookieHeader)
|
|
{
|
|
if (mLoadFlags & LOAD_ANONYMOUS)
|
|
return NS_OK;
|
|
|
|
// empty header isn't an error
|
|
if (!(aCookieHeader && *aCookieHeader))
|
|
return NS_OK;
|
|
|
|
nsICookieService *cs = gHttpHandler->GetCookieService();
|
|
NS_ENSURE_TRUE(cs, NS_ERROR_FAILURE);
|
|
|
|
nsAutoCString date;
|
|
// empty date is not an error
|
|
Unused << mResponseHead->GetHeader(nsHttp::Date, date);
|
|
nsresult rv = cs->SetCookieStringFromHttp(mURI, nullptr, nullptr,
|
|
aCookieHeader, date.get(), this);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
NotifySetCookie(aCookieHeader);
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetThirdPartyFlags(uint32_t *aFlags)
|
|
{
|
|
*aFlags = mThirdPartyFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetThirdPartyFlags(uint32_t aFlags)
|
|
{
|
|
ENSURE_CALLED_BEFORE_ASYNC_OPEN();
|
|
|
|
mThirdPartyFlags = aFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetForceAllowThirdPartyCookie(bool *aForce)
|
|
{
|
|
*aForce = !!(mThirdPartyFlags & nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetForceAllowThirdPartyCookie(bool aForce)
|
|
{
|
|
ENSURE_CALLED_BEFORE_ASYNC_OPEN();
|
|
|
|
if (aForce)
|
|
mThirdPartyFlags |= nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW;
|
|
else
|
|
mThirdPartyFlags &= ~nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetCanceled(bool *aCanceled)
|
|
{
|
|
*aCanceled = mCanceled;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetChannelIsForDownload(bool *aChannelIsForDownload)
|
|
{
|
|
*aChannelIsForDownload = mChannelIsForDownload;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetChannelIsForDownload(bool aChannelIsForDownload)
|
|
{
|
|
mChannelIsForDownload = aChannelIsForDownload;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetCacheKeysRedirectChain(nsTArray<nsCString> *cacheKeys)
|
|
{
|
|
mRedirectedCachekeys = cacheKeys;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLocalAddress(nsACString& addr)
|
|
{
|
|
if (mSelfAddr.raw.family == PR_AF_UNSPEC)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
addr.SetCapacity(kIPv6CStrBufSize);
|
|
NetAddrToString(&mSelfAddr, addr.BeginWriting(), kIPv6CStrBufSize);
|
|
addr.SetLength(strlen(addr.BeginReading()));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::TakeAllSecurityMessages(
|
|
nsCOMArray<nsISecurityConsoleMessage> &aMessages)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
aMessages.Clear();
|
|
for (auto pair : mSecurityConsoleMessages) {
|
|
nsresult rv;
|
|
nsCOMPtr<nsISecurityConsoleMessage> message =
|
|
do_CreateInstance(NS_SECURITY_CONSOLE_MESSAGE_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
message->SetTag(pair.first());
|
|
message->SetCategory(pair.second());
|
|
aMessages.AppendElement(message);
|
|
}
|
|
|
|
MOZ_ASSERT(mSecurityConsoleMessages.Length() == aMessages.Length());
|
|
mSecurityConsoleMessages.Clear();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* Please use this method with care. This can cause the message
|
|
* queue to grow large and cause the channel to take up a lot
|
|
* of memory. Use only static string messages and do not add
|
|
* server side data to the queue, as that can be large.
|
|
* Add only a limited number of messages to the queue to keep
|
|
* the channel size down and do so only in rare erroneous situations.
|
|
* More information can be found here:
|
|
* https://bugzilla.mozilla.org/show_bug.cgi?id=846918
|
|
*/
|
|
nsresult
|
|
HttpBaseChannel::AddSecurityMessage(const nsAString &aMessageTag,
|
|
const nsAString &aMessageCategory)
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsresult rv;
|
|
|
|
// nsSecurityConsoleMessage is not thread-safe refcounted.
|
|
// Delay the object construction until requested.
|
|
// See TakeAllSecurityMessages()
|
|
Pair<nsString, nsString> pair(aMessageTag, aMessageCategory);
|
|
mSecurityConsoleMessages.AppendElement(std::move(pair));
|
|
|
|
nsCOMPtr<nsIConsoleService> console(do_GetService(NS_CONSOLESERVICE_CONTRACTID));
|
|
if (!console) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
nsCOMPtr<nsILoadInfo> loadInfo;
|
|
GetLoadInfo(getter_AddRefs(loadInfo));
|
|
if (!loadInfo) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
auto innerWindowID = loadInfo->GetInnerWindowID();
|
|
|
|
nsAutoString errorText;
|
|
rv = nsContentUtils::GetLocalizedString(
|
|
nsContentUtils::eSECURITY_PROPERTIES,
|
|
NS_ConvertUTF16toUTF8(aMessageTag).get(),
|
|
errorText);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
|
|
error->InitWithSourceURI(errorText, mURI,
|
|
EmptyString(), 0, 0,
|
|
nsIScriptError::warningFlag,
|
|
NS_ConvertUTF16toUTF8(aMessageCategory),
|
|
innerWindowID);
|
|
|
|
console->LogMessage(error);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLocalPort(int32_t* port)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(port);
|
|
|
|
if (mSelfAddr.raw.family == PR_AF_INET) {
|
|
*port = (int32_t)ntohs(mSelfAddr.inet.port);
|
|
}
|
|
else if (mSelfAddr.raw.family == PR_AF_INET6) {
|
|
*port = (int32_t)ntohs(mSelfAddr.inet6.port);
|
|
}
|
|
else
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRemoteAddress(nsACString& addr)
|
|
{
|
|
if (mPeerAddr.raw.family == PR_AF_UNSPEC)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
addr.SetCapacity(kIPv6CStrBufSize);
|
|
NetAddrToString(&mPeerAddr, addr.BeginWriting(), kIPv6CStrBufSize);
|
|
addr.SetLength(strlen(addr.BeginReading()));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRemotePort(int32_t* port)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(port);
|
|
|
|
if (mPeerAddr.raw.family == PR_AF_INET) {
|
|
*port = (int32_t)ntohs(mPeerAddr.inet.port);
|
|
}
|
|
else if (mPeerAddr.raw.family == PR_AF_INET6) {
|
|
*port = (int32_t)ntohs(mPeerAddr.inet6.port);
|
|
}
|
|
else
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::HTTPUpgrade(const nsACString &aProtocolName,
|
|
nsIHttpUpgradeListener *aListener)
|
|
{
|
|
NS_ENSURE_ARG(!aProtocolName.IsEmpty());
|
|
NS_ENSURE_ARG_POINTER(aListener);
|
|
|
|
mUpgradeProtocol = aProtocolName;
|
|
mUpgradeProtocolCallback = aListener;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetAllowSpdy(bool *aAllowSpdy)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aAllowSpdy);
|
|
|
|
*aAllowSpdy = mAllowSpdy;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetAllowSpdy(bool aAllowSpdy)
|
|
{
|
|
mAllowSpdy = aAllowSpdy;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetAllowAltSvc(bool *aAllowAltSvc)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aAllowAltSvc);
|
|
|
|
*aAllowAltSvc = mAllowAltSvc;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetAllowAltSvc(bool aAllowAltSvc)
|
|
{
|
|
mAllowAltSvc = aAllowAltSvc;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetBeConservative(bool *aBeConservative)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aBeConservative);
|
|
|
|
*aBeConservative = mBeConservative;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetBeConservative(bool aBeConservative)
|
|
{
|
|
mBeConservative = aBeConservative;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetTrr(bool *aTRR)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aTRR);
|
|
|
|
*aTRR = mTRR;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetTrr(bool aTRR)
|
|
{
|
|
mTRR = aTRR;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetTlsFlags(uint32_t *aTlsFlags)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aTlsFlags);
|
|
|
|
*aTlsFlags = mTlsFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetTlsFlags(uint32_t aTlsFlags)
|
|
{
|
|
mTlsFlags = aTlsFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetApiRedirectToURI(nsIURI ** aResult)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aResult);
|
|
NS_IF_ADDREF(*aResult = mAPIRedirectToURI);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetResponseTimeoutEnabled(bool *aEnable)
|
|
{
|
|
if (NS_WARN_IF(!aEnable)) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
*aEnable = mResponseTimeoutEnabled;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetResponseTimeoutEnabled(bool aEnable)
|
|
{
|
|
mResponseTimeoutEnabled = aEnable;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetInitialRwin(uint32_t *aRwin)
|
|
{
|
|
if (NS_WARN_IF(!aRwin)) {
|
|
return NS_ERROR_NULL_POINTER;
|
|
}
|
|
*aRwin = mInitialRwin;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetInitialRwin(uint32_t aRwin)
|
|
{
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
mInitialRwin = aRwin;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::ForcePending(bool aForcePending)
|
|
{
|
|
mForcePending = aForcePending;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLastModifiedTime(PRTime* lastModifiedTime)
|
|
{
|
|
if (!mResponseHead)
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
uint32_t lastMod;
|
|
nsresult rv = mResponseHead->GetLastModifiedValue(&lastMod);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
*lastModifiedTime = lastMod;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetCorsIncludeCredentials(bool* aInclude)
|
|
{
|
|
*aInclude = mCorsIncludeCredentials;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetCorsIncludeCredentials(bool aInclude)
|
|
{
|
|
mCorsIncludeCredentials = aInclude;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetCorsMode(uint32_t* aMode)
|
|
{
|
|
*aMode = mCorsMode;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetCorsMode(uint32_t aMode)
|
|
{
|
|
mCorsMode = aMode;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRedirectMode(uint32_t* aMode)
|
|
{
|
|
*aMode = mRedirectMode;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetRedirectMode(uint32_t aMode)
|
|
{
|
|
mRedirectMode = aMode;
|
|
return NS_OK;
|
|
}
|
|
|
|
namespace {
|
|
|
|
bool
|
|
ContainsAllFlags(uint32_t aLoadFlags, uint32_t aMask)
|
|
{
|
|
return (aLoadFlags & aMask) == aMask;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetFetchCacheMode(uint32_t* aFetchCacheMode)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aFetchCacheMode);
|
|
|
|
// Otherwise try to guess an appropriate cache mode from the load flags.
|
|
if (ContainsAllFlags(mLoadFlags, INHIBIT_CACHING | LOAD_BYPASS_CACHE)) {
|
|
*aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE;
|
|
} else if (ContainsAllFlags(mLoadFlags, LOAD_BYPASS_CACHE)) {
|
|
*aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD;
|
|
} else if (ContainsAllFlags(mLoadFlags, VALIDATE_ALWAYS)) {
|
|
*aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE;
|
|
} else if (ContainsAllFlags(mLoadFlags, VALIDATE_NEVER |
|
|
nsICachingChannel::LOAD_ONLY_FROM_CACHE)) {
|
|
*aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED;
|
|
} else if (ContainsAllFlags(mLoadFlags, VALIDATE_NEVER)) {
|
|
*aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE;
|
|
} else {
|
|
*aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
namespace {
|
|
|
|
void
|
|
SetCacheFlags(uint32_t& aLoadFlags, uint32_t aFlags)
|
|
{
|
|
// First, clear any possible cache related flags.
|
|
uint32_t allPossibleFlags = nsIRequest::INHIBIT_CACHING
|
|
| nsIRequest::LOAD_BYPASS_CACHE
|
|
| nsIRequest::VALIDATE_ALWAYS
|
|
| nsIRequest::LOAD_FROM_CACHE
|
|
| nsICachingChannel::LOAD_ONLY_FROM_CACHE;
|
|
aLoadFlags &= ~allPossibleFlags;
|
|
|
|
// Then set the new flags.
|
|
aLoadFlags |= aFlags;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetFetchCacheMode(uint32_t aFetchCacheMode)
|
|
{
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
|
|
// Now, set the load flags that implement each cache mode.
|
|
switch (aFetchCacheMode) {
|
|
case nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT:
|
|
// The "default" mode means to use the http cache normally and
|
|
// respect any http cache-control headers. We effectively want
|
|
// to clear our cache related load flags.
|
|
SetCacheFlags(mLoadFlags, 0);
|
|
break;
|
|
case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE:
|
|
// no-store means don't consult the cache on the way to the network, and
|
|
// don't store the response in the cache even if it's cacheable.
|
|
SetCacheFlags(mLoadFlags, INHIBIT_CACHING | LOAD_BYPASS_CACHE);
|
|
break;
|
|
case nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD:
|
|
// reload means don't consult the cache on the way to the network, but
|
|
// do store the response in the cache if possible.
|
|
SetCacheFlags(mLoadFlags, LOAD_BYPASS_CACHE);
|
|
break;
|
|
case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE:
|
|
// no-cache means always validate what's in the cache.
|
|
SetCacheFlags(mLoadFlags, VALIDATE_ALWAYS);
|
|
break;
|
|
case nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE:
|
|
// force-cache means don't validate unless if the response would vary.
|
|
SetCacheFlags(mLoadFlags, VALIDATE_NEVER);
|
|
break;
|
|
case nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED:
|
|
// only-if-cached means only from cache, no network, no validation, generate
|
|
// a network error if the document was't in the cache.
|
|
// The privacy implications of these flags (making it fast/easy to check if
|
|
// the user has things in their cache without any network traffic side
|
|
// effects) are addressed in the Request constructor which enforces/requires
|
|
// same-origin request mode.
|
|
SetCacheFlags(mLoadFlags, VALIDATE_NEVER |
|
|
nsICachingChannel::LOAD_ONLY_FROM_CACHE);
|
|
break;
|
|
}
|
|
|
|
#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
|
uint32_t finalMode = 0;
|
|
MOZ_ALWAYS_SUCCEEDS(GetFetchCacheMode(&finalMode));
|
|
MOZ_DIAGNOSTIC_ASSERT(finalMode == aFetchCacheMode);
|
|
#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetIntegrityMetadata(const nsAString& aIntegrityMetadata)
|
|
{
|
|
mIntegrityMetadata = aIntegrityMetadata;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetIntegrityMetadata(nsAString& aIntegrityMetadata)
|
|
{
|
|
aIntegrityMetadata = mIntegrityMetadata;
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsISupportsPriority
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetPriority(int32_t *value)
|
|
{
|
|
*value = mPriority;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::AdjustPriority(int32_t delta)
|
|
{
|
|
return SetPriority(mPriority + delta);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIResumableChannel
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetEntityID(nsACString& aEntityID)
|
|
{
|
|
// Don't return an entity ID for Non-GET requests which require
|
|
// additional data
|
|
if (!mRequestHead.IsGet()) {
|
|
return NS_ERROR_NOT_RESUMABLE;
|
|
}
|
|
|
|
uint64_t size = UINT64_MAX;
|
|
nsAutoCString etag, lastmod;
|
|
if (mResponseHead) {
|
|
// Don't return an entity if the server sent the following header:
|
|
// Accept-Ranges: none
|
|
// Not sending the Accept-Ranges header means we can still try
|
|
// sending range requests.
|
|
nsAutoCString acceptRanges;
|
|
Unused << mResponseHead->GetHeader(nsHttp::Accept_Ranges, acceptRanges);
|
|
if (!acceptRanges.IsEmpty() &&
|
|
!nsHttp::FindToken(acceptRanges.get(), "bytes", HTTP_HEADER_VALUE_SEPS)) {
|
|
return NS_ERROR_NOT_RESUMABLE;
|
|
}
|
|
|
|
size = mResponseHead->TotalEntitySize();
|
|
Unused << mResponseHead->GetHeader(nsHttp::Last_Modified, lastmod);
|
|
Unused << mResponseHead->GetHeader(nsHttp::ETag, etag);
|
|
}
|
|
nsCString entityID;
|
|
NS_EscapeURL(etag.BeginReading(), etag.Length(), esc_AlwaysCopy |
|
|
esc_FileBaseName | esc_Forced, entityID);
|
|
entityID.Append('/');
|
|
entityID.AppendInt(int64_t(size));
|
|
entityID.Append('/');
|
|
entityID.Append(lastmod);
|
|
// NOTE: Appending lastmod as the last part avoids having to escape it
|
|
|
|
aEntityID = entityID;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIConsoleReportCollector
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void
|
|
HttpBaseChannel::AddConsoleReport(uint32_t aErrorFlags,
|
|
const nsACString& aCategory,
|
|
nsContentUtils::PropertiesFile aPropertiesFile,
|
|
const nsACString& aSourceFileURI,
|
|
uint32_t aLineNumber, uint32_t aColumnNumber,
|
|
const nsACString& aMessageName,
|
|
const nsTArray<nsString>& aStringParams)
|
|
{
|
|
mReportCollector->AddConsoleReport(aErrorFlags, aCategory, aPropertiesFile,
|
|
aSourceFileURI, aLineNumber,
|
|
aColumnNumber, aMessageName,
|
|
aStringParams);
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::FlushReportsToConsole(uint64_t aInnerWindowID,
|
|
ReportAction aAction)
|
|
{
|
|
mReportCollector->FlushReportsToConsole(aInnerWindowID, aAction);
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::FlushReportsToConsoleForServiceWorkerScope(const nsACString& aScope,
|
|
ReportAction aAction)
|
|
{
|
|
mReportCollector->FlushReportsToConsoleForServiceWorkerScope(aScope, aAction);
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::FlushConsoleReports(nsIDocument* aDocument,
|
|
ReportAction aAction)
|
|
{
|
|
mReportCollector->FlushConsoleReports(aDocument, aAction);
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::FlushConsoleReports(nsILoadGroup* aLoadGroup,
|
|
ReportAction aAction)
|
|
{
|
|
mReportCollector->FlushConsoleReports(aLoadGroup, aAction);
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::FlushConsoleReports(nsIConsoleReportCollector* aCollector)
|
|
{
|
|
mReportCollector->FlushConsoleReports(aCollector);
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::ClearConsoleReports()
|
|
{
|
|
mReportCollector->ClearConsoleReports();
|
|
}
|
|
|
|
nsIPrincipal *
|
|
HttpBaseChannel::GetURIPrincipal()
|
|
{
|
|
if (mPrincipal) {
|
|
return mPrincipal;
|
|
}
|
|
|
|
nsIScriptSecurityManager *securityManager =
|
|
nsContentUtils::GetSecurityManager();
|
|
|
|
if (!securityManager) {
|
|
LOG(("HttpBaseChannel::GetURIPrincipal: No security manager [this=%p]",
|
|
this));
|
|
return nullptr;
|
|
}
|
|
|
|
securityManager->GetChannelURIPrincipal(this, getter_AddRefs(mPrincipal));
|
|
if (!mPrincipal) {
|
|
LOG(("HttpBaseChannel::GetURIPrincipal: No channel principal [this=%p]",
|
|
this));
|
|
return nullptr;
|
|
}
|
|
|
|
return mPrincipal;
|
|
}
|
|
|
|
bool
|
|
HttpBaseChannel::IsNavigation()
|
|
{
|
|
return mForceMainDocumentChannel || (mLoadFlags & LOAD_DOCUMENT_URI);
|
|
}
|
|
|
|
bool
|
|
HttpBaseChannel::BypassServiceWorker() const
|
|
{
|
|
return mLoadFlags & LOAD_BYPASS_SERVICE_WORKER;
|
|
}
|
|
|
|
bool
|
|
HttpBaseChannel::ShouldIntercept(nsIURI* aURI)
|
|
{
|
|
nsCOMPtr<nsINetworkInterceptController> controller;
|
|
GetCallback(controller);
|
|
bool shouldIntercept = false;
|
|
|
|
// We should never intercept internal redirects. The ServiceWorker code
|
|
// can trigger interntal redirects as the result of a FetchEvent. If
|
|
// we re-intercept then an infinite loop can occur.
|
|
//
|
|
// Its also important that we do not set the LOAD_BYPASS_SERVICE_WORKER
|
|
// flag because an internal redirect occurs. Its possible that another
|
|
// interception should occur after the internal redirect. For example,
|
|
// if the ServiceWorker chooses not to call respondWith() the channel
|
|
// will be reset with an internal redirect. If the request is a navigation
|
|
// and the network then triggers a redirect its possible the new URL
|
|
// should be intercepted again.
|
|
//
|
|
// Note, HSTS upgrade redirects are often treated the same as internal
|
|
// redirects. In this case, however, we intentionally allow interception
|
|
// of HSTS upgrade redirects. This matches the expected spec behavior and
|
|
// does not run the risk of infinite loops as described above.
|
|
bool internalRedirect = mLastRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL;
|
|
|
|
if (controller && mLoadInfo && !BypassServiceWorker() && !internalRedirect) {
|
|
nsresult rv = controller->ShouldPrepareForIntercept(aURI ? aURI : mURI.get(),
|
|
this,
|
|
&shouldIntercept);
|
|
if (NS_FAILED(rv)) {
|
|
return false;
|
|
}
|
|
}
|
|
return shouldIntercept;
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::AddAsNonTailRequest()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (EnsureRequestContext()) {
|
|
LOG(("HttpBaseChannel::AddAsNonTailRequest this=%p, rc=%p, already added=%d",
|
|
this, mRequestContext.get(), (bool)mAddedAsNonTailRequest));
|
|
|
|
if (!mAddedAsNonTailRequest) {
|
|
mRequestContext->AddNonTailRequest();
|
|
mAddedAsNonTailRequest = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::RemoveAsNonTailRequest()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mRequestContext) {
|
|
LOG(("HttpBaseChannel::RemoveAsNonTailRequest this=%p, rc=%p, already added=%d",
|
|
this, mRequestContext.get(), (bool)mAddedAsNonTailRequest));
|
|
|
|
if (mAddedAsNonTailRequest) {
|
|
mRequestContext->RemoveNonTailRequest();
|
|
mAddedAsNonTailRequest = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
void HttpBaseChannel::AssertPrivateBrowsingId()
|
|
{
|
|
nsCOMPtr<nsILoadContext> loadContext;
|
|
NS_QueryNotificationCallbacks(this, loadContext);
|
|
// For addons it's possible that mLoadInfo is null.
|
|
if (!mLoadInfo) {
|
|
return;
|
|
}
|
|
|
|
if (!loadContext) {
|
|
return;
|
|
}
|
|
|
|
// We skip testing of favicon loading here since it could be triggered by XUL image
|
|
// which uses SystemPrincipal. The SystemPrincpal doesn't have mPrivateBrowsingId.
|
|
if (nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal()) &&
|
|
mLoadInfo->InternalContentPolicyType() == nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
|
|
return;
|
|
}
|
|
|
|
OriginAttributes docShellAttrs;
|
|
loadContext->GetOriginAttributes(docShellAttrs);
|
|
MOZ_ASSERT(mLoadInfo->GetOriginAttributes().mPrivateBrowsingId == docShellAttrs.mPrivateBrowsingId,
|
|
"PrivateBrowsingId values are not the same between LoadInfo and LoadContext.");
|
|
}
|
|
#endif
|
|
|
|
already_AddRefed<nsILoadInfo>
|
|
HttpBaseChannel::CloneLoadInfoForRedirect(nsIURI * newURI, uint32_t redirectFlags)
|
|
{
|
|
// make a copy of the loadinfo, append to the redirectchain
|
|
// this will be set on the newly created channel for the redirect target.
|
|
if (!mLoadInfo) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsILoadInfo> newLoadInfo =
|
|
static_cast<mozilla::net::LoadInfo*>(mLoadInfo.get())->Clone();
|
|
|
|
nsContentPolicyType contentPolicyType = mLoadInfo->GetExternalContentPolicyType();
|
|
if (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT ||
|
|
contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT) {
|
|
nsCOMPtr<nsIPrincipal> nullPrincipalToInherit = NullPrincipal::CreateWithoutOriginAttributes();
|
|
newLoadInfo->SetPrincipalToInherit(nullPrincipalToInherit);
|
|
}
|
|
|
|
// re-compute the origin attributes of the loadInfo if it's top-level load.
|
|
bool isTopLevelDoc =
|
|
newLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_DOCUMENT;
|
|
|
|
if (isTopLevelDoc) {
|
|
nsCOMPtr<nsILoadContext> loadContext;
|
|
NS_QueryNotificationCallbacks(this, loadContext);
|
|
OriginAttributes docShellAttrs;
|
|
if (loadContext) {
|
|
loadContext->GetOriginAttributes(docShellAttrs);
|
|
}
|
|
|
|
OriginAttributes attrs = newLoadInfo->GetOriginAttributes();
|
|
|
|
MOZ_ASSERT(docShellAttrs.mUserContextId == attrs.mUserContextId,
|
|
"docshell and necko should have the same userContextId attribute.");
|
|
MOZ_ASSERT(docShellAttrs.mInIsolatedMozBrowser == attrs.mInIsolatedMozBrowser,
|
|
"docshell and necko should have the same inIsolatedMozBrowser attribute.");
|
|
MOZ_ASSERT(docShellAttrs.mPrivateBrowsingId == attrs.mPrivateBrowsingId,
|
|
"docshell and necko should have the same privateBrowsingId attribute.");
|
|
|
|
attrs = docShellAttrs;
|
|
attrs.SetFirstPartyDomain(true, newURI);
|
|
newLoadInfo->SetOriginAttributes(attrs);
|
|
}
|
|
|
|
// Leave empty, we want a 'clean ground' when creating the new channel.
|
|
// This will be ensured to be either set by the protocol handler or set
|
|
// to the redirect target URI properly after the channel creation.
|
|
newLoadInfo->SetResultPrincipalURI(nullptr);
|
|
|
|
bool isInternalRedirect =
|
|
(redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
|
|
nsIChannelEventSink::REDIRECT_STS_UPGRADE));
|
|
|
|
nsCString remoteAddress;
|
|
Unused << GetRemoteAddress(remoteAddress);
|
|
nsCOMPtr<nsIRedirectHistoryEntry> entry =
|
|
new nsRedirectHistoryEntry(GetURIPrincipal(), mReferrer, remoteAddress);
|
|
|
|
newLoadInfo->AppendRedirectHistoryEntry(entry, isInternalRedirect);
|
|
|
|
return newLoadInfo.forget();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// nsHttpChannel::nsITraceableChannel
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetNewListener(nsIStreamListener *aListener, nsIStreamListener **_retval)
|
|
{
|
|
LOG(("HttpBaseChannel::SetNewListener [this=%p, mListener=%p, newListener=%p]",
|
|
this, mListener.get(), aListener));
|
|
|
|
if (!mTracingEnabled)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
NS_ENSURE_STATE(mListener);
|
|
NS_ENSURE_ARG_POINTER(aListener);
|
|
|
|
nsCOMPtr<nsIStreamListener> wrapper = new nsStreamListenerWrapper(mListener);
|
|
|
|
wrapper.forget(_retval);
|
|
mListener = aListener;
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel helpers
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void
|
|
HttpBaseChannel::ReleaseListeners()
|
|
{
|
|
MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
|
|
|
|
mListener = nullptr;
|
|
mListenerContext = nullptr;
|
|
mCallbacks = nullptr;
|
|
mProgressSink = nullptr;
|
|
mCompressListener = nullptr;
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::DoNotifyListener()
|
|
{
|
|
LOG(("HttpBaseChannel::DoNotifyListener this=%p", this));
|
|
|
|
if (mListener) {
|
|
MOZ_ASSERT(!mOnStartRequestCalled,
|
|
"We should not call OnStartRequest twice");
|
|
|
|
nsCOMPtr<nsIStreamListener> listener = mListener;
|
|
listener->OnStartRequest(this, mListenerContext);
|
|
|
|
mOnStartRequestCalled = true;
|
|
}
|
|
|
|
// Make sure mIsPending is set to false. At this moment we are done from
|
|
// the point of view of our consumer and we have to report our self
|
|
// as not-pending.
|
|
mIsPending = false;
|
|
|
|
if (mListener) {
|
|
MOZ_ASSERT(!mOnStopRequestCalled,
|
|
"We should not call OnStopRequest twice");
|
|
|
|
nsCOMPtr<nsIStreamListener> listener = mListener;
|
|
listener->OnStopRequest(this, mListenerContext, mStatus);
|
|
|
|
mOnStopRequestCalled = true;
|
|
}
|
|
|
|
// notify "http-on-stop-connect" observers
|
|
gHttpHandler->OnStopRequest(this);
|
|
|
|
// This channel has finished its job, potentially release any tail-blocked
|
|
// requests with this.
|
|
RemoveAsNonTailRequest();
|
|
|
|
// We have to make sure to drop the references to listeners and callbacks
|
|
// no longer needed.
|
|
ReleaseListeners();
|
|
|
|
DoNotifyListenerCleanup();
|
|
|
|
// If this is a navigation, then we must let the docshell flush the reports
|
|
// to the console later. The LoadDocument() is pointing at the detached
|
|
// document that started the navigation. We want to show the reports on the
|
|
// new document. Otherwise the console is wiped and the user never sees
|
|
// the information.
|
|
if (!IsNavigation()) {
|
|
if (mLoadGroup) {
|
|
FlushConsoleReports(mLoadGroup);
|
|
} else if (mLoadInfo) {
|
|
nsCOMPtr<nsIDocument> doc;
|
|
mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
|
|
FlushConsoleReports(doc);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::AddCookiesToRequest()
|
|
{
|
|
if (mLoadFlags & LOAD_ANONYMOUS) {
|
|
return;
|
|
}
|
|
|
|
bool useCookieService =
|
|
(XRE_IsParentProcess());
|
|
nsCString cookie;
|
|
if (useCookieService) {
|
|
nsICookieService *cs = gHttpHandler->GetCookieService();
|
|
if (cs) {
|
|
cs->GetCookieStringFromHttp(mURI,
|
|
nullptr,
|
|
this, getter_Copies(cookie));
|
|
}
|
|
|
|
if (cookie.IsEmpty()) {
|
|
cookie = mUserSetCookieHeader;
|
|
}
|
|
else if (!mUserSetCookieHeader.IsEmpty()) {
|
|
cookie.AppendLiteral("; ");
|
|
cookie.Append(mUserSetCookieHeader);
|
|
}
|
|
}
|
|
else {
|
|
cookie = mUserSetCookieHeader;
|
|
}
|
|
|
|
// If we are in the child process, we want the parent seeing any
|
|
// cookie headers that might have been set by SetRequestHeader()
|
|
SetRequestHeader(nsDependentCString(nsHttp::Cookie), cookie, false);
|
|
}
|
|
|
|
/* static */
|
|
bool
|
|
HttpBaseChannel::IsReferrerSchemeAllowed(nsIURI *aReferrer)
|
|
{
|
|
NS_ENSURE_TRUE(aReferrer, false);
|
|
|
|
nsAutoCString scheme;
|
|
nsresult rv = aReferrer->GetScheme(scheme);
|
|
NS_ENSURE_SUCCESS(rv, false);
|
|
|
|
if (scheme.EqualsIgnoreCase("https") ||
|
|
scheme.EqualsIgnoreCase("http") ||
|
|
scheme.EqualsIgnoreCase("ftp")) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/* static */
|
|
void
|
|
HttpBaseChannel::PropagateReferenceIfNeeded(nsIURI* aURI, nsCOMPtr<nsIURI>& aRedirectURI)
|
|
{
|
|
bool hasRef = false;
|
|
nsresult rv = aRedirectURI->GetHasRef(&hasRef);
|
|
if (NS_SUCCEEDED(rv) && !hasRef) {
|
|
nsAutoCString ref;
|
|
aURI->GetRef(ref);
|
|
if (!ref.IsEmpty()) {
|
|
// NOTE: SetRef will fail if mRedirectURI is immutable
|
|
// (e.g. an about: URI)... Oh well.
|
|
Unused << NS_MutateURI(aRedirectURI)
|
|
.SetRef(ref)
|
|
.Finalize(aRedirectURI);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
HttpBaseChannel::ShouldRewriteRedirectToGET(uint32_t httpStatus,
|
|
nsHttpRequestHead::ParsedMethodType method)
|
|
{
|
|
// for 301 and 302, only rewrite POST
|
|
if (httpStatus == 301 || httpStatus == 302)
|
|
return method == nsHttpRequestHead::kMethod_Post;
|
|
|
|
// rewrite for 303 unless it was HEAD
|
|
if (httpStatus == 303)
|
|
return method != nsHttpRequestHead::kMethod_Head;
|
|
|
|
// otherwise, such as for 307, do not rewrite
|
|
return false;
|
|
}
|
|
|
|
nsresult
|
|
HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI,
|
|
nsIChannel *newChannel,
|
|
bool preserveMethod,
|
|
uint32_t redirectFlags)
|
|
{
|
|
nsresult rv;
|
|
|
|
LOG(("HttpBaseChannel::SetupReplacementChannel "
|
|
"[this=%p newChannel=%p preserveMethod=%d]",
|
|
this, newChannel, preserveMethod));
|
|
|
|
// Ensure the channel's loadInfo's result principal URI so that it's
|
|
// either non-null or updated to the redirect target URI.
|
|
// We must do this because in case the loadInfo's result principal URI
|
|
// is null, it would be taken from OriginalURI of the channel. But we
|
|
// overwrite it with the whole redirect chain first URI before opening
|
|
// the target channel, hence the information would be lost.
|
|
// If the protocol handler that created the channel wants to use
|
|
// the originalURI of the channel as the principal URI, this fulfills
|
|
// that request - newURI is the original URI of the channel.
|
|
nsCOMPtr<nsILoadInfo> newLoadInfo = newChannel->GetLoadInfo();
|
|
if (newLoadInfo) {
|
|
nsCOMPtr<nsIURI> resultPrincipalURI;
|
|
rv = newLoadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (!resultPrincipalURI) {
|
|
rv = newLoadInfo->SetResultPrincipalURI(newURI);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
uint32_t newLoadFlags = mLoadFlags | LOAD_REPLACE;
|
|
// if the original channel was using SSL and this channel is not using
|
|
// SSL, then no need to inhibit persistent caching. however, if the
|
|
// original channel was not using SSL and has INHIBIT_PERSISTENT_CACHING
|
|
// set, then allow the flag to apply to the redirected channel as well.
|
|
// since we force set INHIBIT_PERSISTENT_CACHING on all HTTPS channels,
|
|
// we only need to check if the original channel was using SSL.
|
|
bool usingSSL = false;
|
|
rv = mURI->SchemeIs("https", &usingSSL);
|
|
if (NS_SUCCEEDED(rv) && usingSSL)
|
|
newLoadFlags &= ~INHIBIT_PERSISTENT_CACHING;
|
|
|
|
// Do not pass along LOAD_CHECK_OFFLINE_CACHE
|
|
newLoadFlags &= ~nsICachingChannel::LOAD_CHECK_OFFLINE_CACHE;
|
|
|
|
newChannel->SetLoadGroup(mLoadGroup);
|
|
newChannel->SetNotificationCallbacks(mCallbacks);
|
|
newChannel->SetLoadFlags(newLoadFlags);
|
|
|
|
nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(newChannel));
|
|
if (cos) {
|
|
cos->SetClassFlags(mClassOfService);
|
|
}
|
|
|
|
// Try to preserve the privacy bit if it has been overridden
|
|
if (mPrivateBrowsingOverriden) {
|
|
nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel =
|
|
do_QueryInterface(newChannel);
|
|
if (newPBChannel) {
|
|
newPBChannel->SetPrivate(mPrivateBrowsing);
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
|
|
if (!httpChannel)
|
|
return NS_OK; // no other options to set
|
|
|
|
// Preserve the CORS preflight information.
|
|
nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(newChannel);
|
|
if (httpInternal) {
|
|
httpInternal->SetLastRedirectFlags(redirectFlags);
|
|
|
|
if (mRequireCORSPreflight) {
|
|
httpInternal->SetCorsPreflightParameters(mUnsafeHeaders);
|
|
}
|
|
}
|
|
|
|
if (preserveMethod) {
|
|
nsCOMPtr<nsIUploadChannel> uploadChannel =
|
|
do_QueryInterface(httpChannel);
|
|
nsCOMPtr<nsIUploadChannel2> uploadChannel2 =
|
|
do_QueryInterface(httpChannel);
|
|
if (mUploadStream && (uploadChannel2 || uploadChannel)) {
|
|
// rewind upload stream
|
|
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
|
|
MOZ_ASSERT(seekable);
|
|
|
|
seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
|
|
|
|
// replicate original call to SetUploadStream...
|
|
if (uploadChannel2) {
|
|
nsAutoCString ctype;
|
|
// If header is not present mRequestHead.HasHeaderValue will truncated
|
|
// it. But we want to end up with a void string, not an empty string,
|
|
// because ExplicitSetUploadStream treats the former as "no header" and
|
|
// the latter as "header with empty string value".
|
|
nsresult ctypeOK = mRequestHead.GetHeader(nsHttp::Content_Type, ctype);
|
|
if (NS_FAILED(ctypeOK)) {
|
|
ctype.SetIsVoid(true);
|
|
}
|
|
nsAutoCString clen;
|
|
Unused << mRequestHead.GetHeader(nsHttp::Content_Length, clen);
|
|
nsAutoCString method;
|
|
mRequestHead.Method(method);
|
|
int64_t len = clen.IsEmpty() ? -1 : nsCRT::atoll(clen.get());
|
|
uploadChannel2->ExplicitSetUploadStream(
|
|
mUploadStream, ctype, len,
|
|
method,
|
|
mUploadStreamHasHeaders);
|
|
} else {
|
|
if (mUploadStreamHasHeaders) {
|
|
uploadChannel->SetUploadStream(mUploadStream, EmptyCString(),
|
|
-1);
|
|
} else {
|
|
nsAutoCString ctype;
|
|
if (NS_FAILED(mRequestHead.GetHeader(nsHttp::Content_Type, ctype))) {
|
|
ctype = NS_LITERAL_CSTRING("application/octet-stream");
|
|
}
|
|
nsAutoCString clen;
|
|
if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Content_Length, clen))
|
|
&&
|
|
!clen.IsEmpty()) {
|
|
uploadChannel->SetUploadStream(mUploadStream,
|
|
ctype,
|
|
nsCRT::atoll(clen.get()));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// since preserveMethod is true, we need to ensure that the appropriate
|
|
// request method gets set on the channel, regardless of whether or not
|
|
// we set the upload stream above. This means SetRequestMethod() will
|
|
// be called twice if ExplicitSetUploadStream() gets called above.
|
|
|
|
nsAutoCString method;
|
|
mRequestHead.Method(method);
|
|
rv = httpChannel->SetRequestMethod(method);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}
|
|
// convey the referrer if one was used for this channel to the next one
|
|
if (mReferrer) {
|
|
rv = httpChannel->SetReferrerWithPolicy(mReferrer, mReferrerPolicy);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}
|
|
// convey the mAllowSTS flags
|
|
rv = httpChannel->SetAllowSTS(mAllowSTS);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
// convey the Accept header value
|
|
{
|
|
nsAutoCString oldAcceptValue;
|
|
nsresult hasHeader = mRequestHead.GetHeader(nsHttp::Accept, oldAcceptValue);
|
|
if (NS_SUCCEEDED(hasHeader)) {
|
|
rv = httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
|
|
oldAcceptValue,
|
|
false);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}
|
|
}
|
|
|
|
// share the request context - see bug 1236650
|
|
rv = httpChannel->SetRequestContextID(mRequestContextID);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
// When on the parent process, the channel can't attempt to get it itself.
|
|
// When on the child process, it would be waste to query it again.
|
|
rv = httpChannel->SetTopLevelOuterContentWindowId(mTopLevelOuterContentWindowId);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
// Not setting this flag would break carrying permissions down to the child process
|
|
// when the channel is artificially forced to be a main document load.
|
|
rv = httpChannel->SetIsMainDocumentChannel(mForceMainDocumentChannel);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
// Preserve the loading order
|
|
nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(newChannel);
|
|
if (p) {
|
|
p->SetPriority(mPriority);
|
|
}
|
|
|
|
if (httpInternal) {
|
|
// Convey third party cookie, conservative, and spdy flags.
|
|
rv = httpInternal->SetThirdPartyFlags(mThirdPartyFlags);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
rv = httpInternal->SetAllowSpdy(mAllowSpdy);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
rv = httpInternal->SetAllowAltSvc(mAllowAltSvc);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
rv = httpInternal->SetBeConservative(mBeConservative);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
rv = httpInternal->SetTrr(mTRR);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
rv = httpInternal->SetTlsFlags(mTlsFlags);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
RefPtr<nsHttpChannel> realChannel;
|
|
CallQueryInterface(newChannel, realChannel.StartAssignment());
|
|
if (realChannel) {
|
|
rv = realChannel->SetTopWindowURI(mTopWindowURI);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}
|
|
|
|
// update the DocumentURI indicator since we are being redirected.
|
|
// if this was a top-level document channel, then the new channel
|
|
// should have its mDocumentURI point to newURI; otherwise, we
|
|
// just need to pass along our mDocumentURI to the new channel.
|
|
if (newURI && (mURI == mDocumentURI))
|
|
rv = httpInternal->SetDocumentURI(newURI);
|
|
else
|
|
rv = httpInternal->SetDocumentURI(mDocumentURI);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
// if there is a chain of keys for redirect-responses we transfer it to
|
|
// the new channel (see bug #561276)
|
|
if (mRedirectedCachekeys) {
|
|
LOG(("HttpBaseChannel::SetupReplacementChannel "
|
|
"[this=%p] transferring chain of redirect cache-keys", this));
|
|
rv = httpInternal->SetCacheKeysRedirectChain(mRedirectedCachekeys.forget());
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}
|
|
|
|
// Preserve CORS mode flag.
|
|
rv = httpInternal->SetCorsMode(mCorsMode);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
// Preserve Redirect mode flag.
|
|
rv = httpInternal->SetRedirectMode(mRedirectMode);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
// Preserve Integrity metadata.
|
|
rv = httpInternal->SetIntegrityMetadata(mIntegrityMetadata);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
httpInternal->SetAltDataForChild(mAltDataForChild);
|
|
}
|
|
|
|
// transfer application cache information
|
|
nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel =
|
|
do_QueryInterface(newChannel);
|
|
if (appCacheChannel) {
|
|
appCacheChannel->SetApplicationCache(mApplicationCache);
|
|
appCacheChannel->SetInheritApplicationCache(mInheritApplicationCache);
|
|
// We purposely avoid transfering mChooseApplicationCache.
|
|
}
|
|
|
|
// transfer any properties
|
|
nsCOMPtr<nsIWritablePropertyBag> bag(do_QueryInterface(newChannel));
|
|
if (bag) {
|
|
for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) {
|
|
bag->SetProperty(iter.Key(), iter.UserData());
|
|
}
|
|
}
|
|
|
|
// Transfer the timing data (if we are dealing with an nsITimedChannel).
|
|
nsCOMPtr<nsITimedChannel> newTimedChannel(do_QueryInterface(newChannel));
|
|
nsCOMPtr<nsITimedChannel> oldTimedChannel(
|
|
do_QueryInterface(static_cast<nsIHttpChannel*>(this)));
|
|
if (oldTimedChannel && newTimedChannel) {
|
|
newTimedChannel->SetTimingEnabled(mTimingEnabled);
|
|
|
|
if (redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
|
|
newTimedChannel->SetRedirectCount(mRedirectCount);
|
|
int8_t newCount = mInternalRedirectCount + 1;
|
|
newTimedChannel->SetInternalRedirectCount(
|
|
std::max(newCount, mInternalRedirectCount));
|
|
} else {
|
|
int8_t newCount = mRedirectCount + 1;
|
|
newTimedChannel->SetRedirectCount(
|
|
std::max(newCount, mRedirectCount));
|
|
newTimedChannel->SetInternalRedirectCount(mInternalRedirectCount);
|
|
}
|
|
|
|
TimeStamp oldAsyncOpenTime;
|
|
oldTimedChannel->GetAsyncOpen(&oldAsyncOpenTime);
|
|
|
|
if (redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
|
|
TimeStamp oldChannelCreationTimestamp;
|
|
oldTimedChannel->GetChannelCreation(&oldChannelCreationTimestamp);
|
|
|
|
if (!oldChannelCreationTimestamp.IsNull()) {
|
|
newTimedChannel->SetChannelCreation(oldChannelCreationTimestamp);
|
|
}
|
|
|
|
if (!oldAsyncOpenTime.IsNull()) {
|
|
newTimedChannel->SetAsyncOpen(oldAsyncOpenTime);
|
|
}
|
|
}
|
|
|
|
// If the RedirectStart is null, we will use the AsyncOpen value of the
|
|
// previous channel (this is the first redirect in the redirects chain).
|
|
if (mRedirectStartTimeStamp.IsNull()) {
|
|
// Only do this for real redirects. Internal redirects should be hidden.
|
|
if (!(redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
|
|
newTimedChannel->SetRedirectStart(oldAsyncOpenTime);
|
|
}
|
|
} else {
|
|
newTimedChannel->SetRedirectStart(mRedirectStartTimeStamp);
|
|
}
|
|
|
|
// For internal redirects just propagate the last redirect end time
|
|
// forward. Otherwise the new redirect end time is the last response
|
|
// end time.
|
|
TimeStamp newRedirectEnd;
|
|
if (redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
|
|
oldTimedChannel->GetRedirectEnd(&newRedirectEnd);
|
|
} else {
|
|
oldTimedChannel->GetResponseEnd(&newRedirectEnd);
|
|
}
|
|
newTimedChannel->SetRedirectEnd(newRedirectEnd);
|
|
|
|
nsAutoString initiatorType;
|
|
oldTimedChannel->GetInitiatorType(initiatorType);
|
|
newTimedChannel->SetInitiatorType(initiatorType);
|
|
|
|
// Check whether or not this was a cross-domain redirect.
|
|
newTimedChannel->SetAllRedirectsSameOrigin(
|
|
mAllRedirectsSameOrigin && SameOriginWithOriginalUri(newURI));
|
|
|
|
// Execute the timing allow check to determine whether
|
|
// to report the redirect timing info
|
|
nsCOMPtr<nsILoadInfo> loadInfo;
|
|
GetLoadInfo(getter_AddRefs(loadInfo));
|
|
// TYPE_DOCUMENT loads don't have a loadingPrincipal, so we can't set
|
|
// AllRedirectsPassTimingAllowCheck on them.
|
|
if (loadInfo && loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT) {
|
|
nsCOMPtr<nsIPrincipal> principal = loadInfo->LoadingPrincipal();
|
|
newTimedChannel->SetAllRedirectsPassTimingAllowCheck(
|
|
mAllRedirectsPassTimingAllowCheck &&
|
|
oldTimedChannel->TimingAllowCheck(principal));
|
|
}
|
|
|
|
// Propagate service worker measurements across redirects. The
|
|
// PeformanceResourceTiming.workerStart API expects to see the
|
|
// worker start time after a redirect.
|
|
newTimedChannel->SetLaunchServiceWorkerStart(mLaunchServiceWorkerStart);
|
|
newTimedChannel->SetLaunchServiceWorkerEnd(mLaunchServiceWorkerEnd);
|
|
newTimedChannel->SetDispatchFetchEventStart(mDispatchFetchEventStart);
|
|
newTimedChannel->SetDispatchFetchEventEnd(mDispatchFetchEventEnd);
|
|
newTimedChannel->SetHandleFetchEventStart(mHandleFetchEventStart);
|
|
newTimedChannel->SetHandleFetchEventEnd(mHandleFetchEventEnd);
|
|
}
|
|
|
|
// Pass the preferred alt-data type on to the new channel.
|
|
nsCOMPtr<nsICacheInfoChannel> cacheInfoChan(do_QueryInterface(newChannel));
|
|
if (cacheInfoChan) {
|
|
cacheInfoChan->PreferAlternativeDataType(mPreferredCachedAltDataType);
|
|
}
|
|
|
|
if (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
|
|
nsIChannelEventSink::REDIRECT_STS_UPGRADE)) {
|
|
// Copy non-origin related headers to the new channel.
|
|
nsCOMPtr<nsIHttpHeaderVisitor> visitor =
|
|
new AddHeadersToChannelVisitor(httpChannel);
|
|
rv = mRequestHead.VisitHeaders(visitor);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}
|
|
|
|
// This channel has been redirected. Don't report timing info.
|
|
mTimingEnabled = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
// Redirect Tracking
|
|
bool
|
|
HttpBaseChannel::SameOriginWithOriginalUri(nsIURI *aURI)
|
|
{
|
|
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
|
nsresult rv = ssm->CheckSameOriginURI(aURI, mOriginalURI, false);
|
|
return (NS_SUCCEEDED(rv));
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIClassifiedChannel
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetMatchedList(nsACString& aList)
|
|
{
|
|
aList = mMatchedList;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetMatchedProvider(nsACString& aProvider)
|
|
{
|
|
aProvider = mMatchedProvider;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetMatchedFullHash(nsACString& aFullHash)
|
|
{
|
|
aFullHash = mMatchedFullHash;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetMatchedInfo(const nsACString& aList,
|
|
const nsACString& aProvider,
|
|
const nsACString& aFullHash) {
|
|
NS_ENSURE_ARG(!aList.IsEmpty());
|
|
|
|
mMatchedList = aList;
|
|
mMatchedProvider = aProvider;
|
|
mMatchedFullHash = aFullHash;
|
|
return NS_OK;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsITimedChannel
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetTimingEnabled(bool enabled) {
|
|
mTimingEnabled = enabled;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetTimingEnabled(bool* _retval) {
|
|
*_retval = mTimingEnabled;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetChannelCreation(TimeStamp* _retval) {
|
|
*_retval = mChannelCreationTimestamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetChannelCreation(TimeStamp aValue) {
|
|
MOZ_DIAGNOSTIC_ASSERT(!aValue.IsNull());
|
|
TimeDuration adjust = aValue - mChannelCreationTimestamp;
|
|
mChannelCreationTimestamp = aValue;
|
|
mChannelCreationTime += (PRTime)adjust.ToMicroseconds();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetAsyncOpen(TimeStamp* _retval) {
|
|
*_retval = mAsyncOpenTime;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetAsyncOpen(TimeStamp aValue) {
|
|
MOZ_DIAGNOSTIC_ASSERT(!aValue.IsNull());
|
|
mAsyncOpenTime = aValue;
|
|
mAsyncOpenTimeOverriden = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* @return the number of redirects. There is no check for cross-domain
|
|
* redirects. This check must be done by the consumers.
|
|
*/
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRedirectCount(uint8_t *aRedirectCount)
|
|
{
|
|
*aRedirectCount = mRedirectCount;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetRedirectCount(uint8_t aRedirectCount)
|
|
{
|
|
mRedirectCount = aRedirectCount;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetInternalRedirectCount(uint8_t *aRedirectCount)
|
|
{
|
|
*aRedirectCount = mInternalRedirectCount;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetInternalRedirectCount(uint8_t aRedirectCount)
|
|
{
|
|
mInternalRedirectCount = aRedirectCount;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRedirectStart(TimeStamp* _retval)
|
|
{
|
|
*_retval = mRedirectStartTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetRedirectStart(TimeStamp aRedirectStart)
|
|
{
|
|
mRedirectStartTimeStamp = aRedirectStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRedirectEnd(TimeStamp* _retval)
|
|
{
|
|
*_retval = mRedirectEndTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetRedirectEnd(TimeStamp aRedirectEnd)
|
|
{
|
|
mRedirectEndTimeStamp = aRedirectEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetAllRedirectsSameOrigin(bool *aAllRedirectsSameOrigin)
|
|
{
|
|
*aAllRedirectsSameOrigin = mAllRedirectsSameOrigin;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetAllRedirectsSameOrigin(bool aAllRedirectsSameOrigin)
|
|
{
|
|
mAllRedirectsSameOrigin = aAllRedirectsSameOrigin;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetAllRedirectsPassTimingAllowCheck(bool *aPassesCheck)
|
|
{
|
|
*aPassesCheck = mAllRedirectsPassTimingAllowCheck;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetAllRedirectsPassTimingAllowCheck(bool aPassesCheck)
|
|
{
|
|
mAllRedirectsPassTimingAllowCheck = aPassesCheck;
|
|
return NS_OK;
|
|
}
|
|
|
|
// http://www.w3.org/TR/resource-timing/#timing-allow-check
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::TimingAllowCheck(nsIPrincipal *aOrigin, bool *_retval)
|
|
{
|
|
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
|
|
nsCOMPtr<nsIPrincipal> resourcePrincipal;
|
|
nsresult rv = ssm->GetChannelURIPrincipal(this, getter_AddRefs(resourcePrincipal));
|
|
if (NS_FAILED(rv) || !resourcePrincipal || !aOrigin) {
|
|
*_retval = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
bool sameOrigin = false;
|
|
rv = resourcePrincipal->Equals(aOrigin, &sameOrigin);
|
|
if (NS_SUCCEEDED(rv) && sameOrigin) {
|
|
*_retval = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsAutoCString headerValue;
|
|
rv = GetResponseHeader(NS_LITERAL_CSTRING("Timing-Allow-Origin"), headerValue);
|
|
if (NS_FAILED(rv)) {
|
|
*_retval = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsAutoCString origin;
|
|
nsContentUtils::GetASCIIOrigin(aOrigin, origin);
|
|
|
|
Tokenizer p(headerValue);
|
|
Tokenizer::Token t;
|
|
|
|
p.Record();
|
|
nsAutoCString headerItem;
|
|
while (p.Next(t)) {
|
|
if (t.Type() == Tokenizer::TOKEN_EOF ||
|
|
t.Equals(Tokenizer::Token::Char(','))) {
|
|
p.Claim(headerItem);
|
|
headerItem.StripWhitespace();
|
|
// If the list item contains a case-sensitive match for the value of the
|
|
// origin, or a wildcard, return pass
|
|
if (headerItem == origin || headerItem == "*") {
|
|
*_retval = true;
|
|
return NS_OK;
|
|
}
|
|
// We start recording again for the following items in the list
|
|
p.Record();
|
|
}
|
|
}
|
|
|
|
*_retval = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLaunchServiceWorkerStart(TimeStamp* _retval) {
|
|
MOZ_ASSERT(_retval);
|
|
*_retval = mLaunchServiceWorkerStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) {
|
|
mLaunchServiceWorkerStart = aTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLaunchServiceWorkerEnd(TimeStamp* _retval) {
|
|
MOZ_ASSERT(_retval);
|
|
*_retval = mLaunchServiceWorkerEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetLaunchServiceWorkerEnd(TimeStamp aTimeStamp) {
|
|
mLaunchServiceWorkerEnd = aTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetDispatchFetchEventStart(TimeStamp* _retval) {
|
|
MOZ_ASSERT(_retval);
|
|
*_retval = mDispatchFetchEventStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetDispatchFetchEventStart(TimeStamp aTimeStamp) {
|
|
mDispatchFetchEventStart = aTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetDispatchFetchEventEnd(TimeStamp* _retval) {
|
|
MOZ_ASSERT(_retval);
|
|
*_retval = mDispatchFetchEventEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetDispatchFetchEventEnd(TimeStamp aTimeStamp) {
|
|
mDispatchFetchEventEnd = aTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetHandleFetchEventStart(TimeStamp* _retval) {
|
|
MOZ_ASSERT(_retval);
|
|
*_retval = mHandleFetchEventStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetHandleFetchEventStart(TimeStamp aTimeStamp) {
|
|
mHandleFetchEventStart = aTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetHandleFetchEventEnd(TimeStamp* _retval) {
|
|
MOZ_ASSERT(_retval);
|
|
*_retval = mHandleFetchEventEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetHandleFetchEventEnd(TimeStamp aTimeStamp) {
|
|
mHandleFetchEventEnd = aTimeStamp;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetDomainLookupStart(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.domainLookupStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetDomainLookupEnd(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.domainLookupEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetConnectStart(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.connectStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetTcpConnectEnd(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.tcpConnectEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetSecureConnectionStart(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.secureConnectionStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetConnectEnd(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.connectEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetRequestStart(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.requestStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetResponseStart(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.responseStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetResponseEnd(TimeStamp* _retval) {
|
|
*_retval = mTransactionTimings.responseEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetCacheReadStart(TimeStamp* _retval) {
|
|
*_retval = mCacheReadStart;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetCacheReadEnd(TimeStamp* _retval) {
|
|
*_retval = mCacheReadEnd;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetInitiatorType(nsAString & aInitiatorType)
|
|
{
|
|
aInitiatorType = mInitiatorType;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetInitiatorType(const nsAString & aInitiatorType)
|
|
{
|
|
mInitiatorType = aInitiatorType;
|
|
return NS_OK;
|
|
}
|
|
|
|
#define IMPL_TIMING_ATTR(name) \
|
|
NS_IMETHODIMP \
|
|
HttpBaseChannel::Get##name##Time(PRTime* _retval) { \
|
|
TimeStamp stamp; \
|
|
Get##name(&stamp); \
|
|
if (stamp.IsNull()) { \
|
|
*_retval = 0; \
|
|
return NS_OK; \
|
|
} \
|
|
*_retval = mChannelCreationTime + \
|
|
(PRTime) ((stamp - mChannelCreationTimestamp).ToSeconds() * 1e6); \
|
|
return NS_OK; \
|
|
}
|
|
|
|
IMPL_TIMING_ATTR(ChannelCreation)
|
|
IMPL_TIMING_ATTR(AsyncOpen)
|
|
IMPL_TIMING_ATTR(LaunchServiceWorkerStart)
|
|
IMPL_TIMING_ATTR(LaunchServiceWorkerEnd)
|
|
IMPL_TIMING_ATTR(DispatchFetchEventStart)
|
|
IMPL_TIMING_ATTR(DispatchFetchEventEnd)
|
|
IMPL_TIMING_ATTR(HandleFetchEventStart)
|
|
IMPL_TIMING_ATTR(HandleFetchEventEnd)
|
|
IMPL_TIMING_ATTR(DomainLookupStart)
|
|
IMPL_TIMING_ATTR(DomainLookupEnd)
|
|
IMPL_TIMING_ATTR(ConnectStart)
|
|
IMPL_TIMING_ATTR(TcpConnectEnd)
|
|
IMPL_TIMING_ATTR(SecureConnectionStart)
|
|
IMPL_TIMING_ATTR(ConnectEnd)
|
|
IMPL_TIMING_ATTR(RequestStart)
|
|
IMPL_TIMING_ATTR(ResponseStart)
|
|
IMPL_TIMING_ATTR(ResponseEnd)
|
|
IMPL_TIMING_ATTR(CacheReadStart)
|
|
IMPL_TIMING_ATTR(CacheReadEnd)
|
|
IMPL_TIMING_ATTR(RedirectStart)
|
|
IMPL_TIMING_ATTR(RedirectEnd)
|
|
|
|
#undef IMPL_TIMING_ATTR
|
|
|
|
mozilla::dom::PerformanceStorage*
|
|
HttpBaseChannel::GetPerformanceStorage()
|
|
{
|
|
// If performance timing is disabled, there is no need for the Performance
|
|
// object anymore.
|
|
if (!mTimingEnabled) {
|
|
return nullptr;
|
|
}
|
|
|
|
// There is no point in continuing, since the performance object in the parent
|
|
// isn't the same as the one in the child which will be reporting resource performance.
|
|
if (XRE_IsE10sParentProcess()) {
|
|
return nullptr;
|
|
}
|
|
|
|
if (!mLoadInfo) {
|
|
return nullptr;
|
|
}
|
|
|
|
// If a custom performance storage is set, let's use it.
|
|
mozilla::dom::PerformanceStorage* performanceStorage = mLoadInfo->GetPerformanceStorage();
|
|
if (performanceStorage) {
|
|
return performanceStorage;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocument> loadingDocument;
|
|
mLoadInfo->GetLoadingDocument(getter_AddRefs(loadingDocument));
|
|
if (!loadingDocument) {
|
|
return nullptr;
|
|
}
|
|
|
|
// We only add to the document's performance object if it has the same
|
|
// principal as the one triggering the load. This is to prevent navigations
|
|
// triggered _by_ the iframe from showing up in the parent document's
|
|
// performance entries if they have different origins.
|
|
if (!mLoadInfo->TriggeringPrincipal()->Equals(loadingDocument->NodePrincipal())) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowInner> innerWindow = loadingDocument->GetInnerWindow();
|
|
if (!innerWindow) {
|
|
return nullptr;
|
|
}
|
|
|
|
mozilla::dom::Performance* performance = innerWindow->GetPerformance();
|
|
if (!performance) {
|
|
return nullptr;
|
|
}
|
|
|
|
return performance->AsPerformanceStorage();
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::MaybeReportTimingData()
|
|
{
|
|
mozilla::dom::PerformanceStorage* documentPerformance = GetPerformanceStorage();
|
|
if (documentPerformance) {
|
|
documentPerformance->AddEntry(this, this);
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetReportResourceTiming(bool enabled) {
|
|
mReportTiming = enabled;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetReportResourceTiming(bool* _retval) {
|
|
*_retval = mReportTiming;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsIURI*
|
|
HttpBaseChannel::GetReferringPage()
|
|
{
|
|
nsCOMPtr<nsPIDOMWindowInner> pDomWindow = GetInnerDOMWindow();
|
|
if (!pDomWindow) {
|
|
return nullptr;
|
|
}
|
|
return pDomWindow->GetDocumentURI();
|
|
}
|
|
|
|
nsPIDOMWindowInner*
|
|
HttpBaseChannel::GetInnerDOMWindow()
|
|
{
|
|
nsCOMPtr<nsILoadContext> loadContext;
|
|
NS_QueryNotificationCallbacks(this, loadContext);
|
|
if (!loadContext) {
|
|
return nullptr;
|
|
}
|
|
nsCOMPtr<mozIDOMWindowProxy> domWindow;
|
|
loadContext->GetAssociatedWindow(getter_AddRefs(domWindow));
|
|
if (!domWindow) {
|
|
return nullptr;
|
|
}
|
|
auto* pDomWindow = nsPIDOMWindowOuter::From(domWindow);
|
|
if (!pDomWindow) {
|
|
return nullptr;
|
|
}
|
|
nsCOMPtr<nsPIDOMWindowInner> innerWindow = pDomWindow->GetCurrentInnerWindow();
|
|
if (!innerWindow) {
|
|
return nullptr;
|
|
}
|
|
|
|
return innerWindow;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// HttpBaseChannel::nsIThrottledInputChannel
|
|
//-----------------------------------------------------------------------------
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetThrottleQueue(nsIInputChannelThrottleQueue* aQueue)
|
|
{
|
|
if (!XRE_IsParentProcess()) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
mThrottleQueue = aQueue;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetThrottleQueue(nsIInputChannelThrottleQueue** aQueue)
|
|
{
|
|
*aQueue = mThrottleQueue;
|
|
return NS_OK;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
bool
|
|
HttpBaseChannel::EnsureRequestContextID()
|
|
{
|
|
if (mRequestContextID) {
|
|
// Already have a request context ID, no need to do the rest of this work
|
|
LOG(("HttpBaseChannel::EnsureRequestContextID this=%p id=%" PRIx64,
|
|
this, mRequestContextID));
|
|
return true;
|
|
}
|
|
|
|
// Find the loadgroup at the end of the chain in order
|
|
// to make sure all channels derived from the load group
|
|
// use the same connection scope.
|
|
nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(mLoadGroup);
|
|
if (!childLoadGroup) {
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsILoadGroup> rootLoadGroup;
|
|
childLoadGroup->GetRootLoadGroup(getter_AddRefs(rootLoadGroup));
|
|
if (!rootLoadGroup) {
|
|
return false;
|
|
}
|
|
|
|
// Set the load group connection scope on this channel and its transaction
|
|
rootLoadGroup->GetRequestContextID(&mRequestContextID);
|
|
|
|
LOG(("HttpBaseChannel::EnsureRequestContextID this=%p id=%" PRIx64,
|
|
this, mRequestContextID));
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
HttpBaseChannel::EnsureRequestContext()
|
|
{
|
|
if (mRequestContext) {
|
|
// Already have a request context, no need to do the rest of this work
|
|
return true;
|
|
}
|
|
|
|
if (!EnsureRequestContextID()) {
|
|
return false;
|
|
}
|
|
|
|
nsIRequestContextService* rcsvc = gHttpHandler->GetRequestContextService();
|
|
if (!rcsvc) {
|
|
return false;
|
|
}
|
|
|
|
rcsvc->GetRequestContext(mRequestContextID, getter_AddRefs(mRequestContext));
|
|
if (!mRequestContext) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::EnsureTopLevelOuterContentWindowId()
|
|
{
|
|
if (mTopLevelOuterContentWindowId) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsILoadContext> loadContext;
|
|
GetCallback(loadContext);
|
|
if (!loadContext) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<mozIDOMWindowProxy> topWindow;
|
|
loadContext->GetTopWindow(getter_AddRefs(topWindow));
|
|
if (!topWindow) {
|
|
return;
|
|
}
|
|
|
|
mTopLevelOuterContentWindowId =
|
|
nsPIDOMWindowOuter::From(topWindow)->WindowID();
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::SetCorsPreflightParameters(const nsTArray<nsCString>& aUnsafeHeaders)
|
|
{
|
|
MOZ_RELEASE_ASSERT(!mRequestObserversCalled);
|
|
|
|
mRequireCORSPreflight = true;
|
|
mUnsafeHeaders = aUnsafeHeaders;
|
|
}
|
|
|
|
void
|
|
HttpBaseChannel::SetAltDataForChild(bool aIsForChild)
|
|
{
|
|
mAltDataForChild = aIsForChild;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetBlockAuthPrompt(bool* aValue)
|
|
{
|
|
if (!aValue) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
*aValue = mBlockAuthPrompt;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetBlockAuthPrompt(bool aValue)
|
|
{
|
|
ENSURE_CALLED_BEFORE_CONNECT();
|
|
|
|
mBlockAuthPrompt = aValue;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetConnectionInfoHashKey(nsACString& aConnectionInfoHashKey)
|
|
{
|
|
if (!mConnectionInfo) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
aConnectionInfoHashKey.Assign(mConnectionInfo->HashKey());
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetLastRedirectFlags(uint32_t *aValue)
|
|
{
|
|
NS_ENSURE_ARG(aValue);
|
|
*aValue = mLastRedirectFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetLastRedirectFlags(uint32_t aValue)
|
|
{
|
|
mLastRedirectFlags = aValue;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetNavigationStartTimeStamp(TimeStamp* aTimeStamp)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::SetNavigationStartTimeStamp(TimeStamp aTimeStamp)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
nsresult
|
|
HttpBaseChannel::CheckRedirectLimit(uint32_t aRedirectFlags) const
|
|
{
|
|
if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
|
|
// Some platform features, like Service Workers, depend on internal
|
|
// redirects. We should allow some number of internal redirects above
|
|
// and beyond the normal redirect limit so these features continue
|
|
// to work.
|
|
static const int8_t kMinInternalRedirects = 5;
|
|
|
|
if (mInternalRedirectCount >= (mRedirectionLimit + kMinInternalRedirects)) {
|
|
LOG(("internal redirection limit reached!\n"));
|
|
return NS_ERROR_REDIRECT_LOOP;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
MOZ_ASSERT(aRedirectFlags & (nsIChannelEventSink::REDIRECT_TEMPORARY |
|
|
nsIChannelEventSink::REDIRECT_PERMANENT |
|
|
nsIChannelEventSink::REDIRECT_STS_UPGRADE));
|
|
|
|
if (mRedirectCount >= mRedirectionLimit) {
|
|
LOG(("redirection limit reached!\n"));
|
|
return NS_ERROR_REDIRECT_LOOP;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// NOTE: This function duplicates code from nsBaseChannel. This will go away
|
|
// once HTTP uses nsBaseChannel (part of bug 312760)
|
|
/* static */ void
|
|
HttpBaseChannel::CallTypeSniffers(void *aClosure, const uint8_t *aData,
|
|
uint32_t aCount)
|
|
{
|
|
nsIChannel *chan = static_cast<nsIChannel*>(aClosure);
|
|
|
|
nsAutoCString newType;
|
|
NS_SniffContent(NS_CONTENT_SNIFFER_CATEGORY, chan, aData, aCount, newType);
|
|
if (!newType.IsEmpty()) {
|
|
chan->SetContentType(newType);
|
|
}
|
|
}
|
|
|
|
template <class T>
|
|
static void
|
|
ParseServerTimingHeader(const nsAutoPtr<T> &aHeader,
|
|
nsTArray<nsCOMPtr<nsIServerTiming>>& aOutput)
|
|
{
|
|
if (!aHeader) {
|
|
return;
|
|
}
|
|
|
|
nsAutoCString serverTimingHeader;
|
|
Unused << aHeader->GetHeader(nsHttp::Server_Timing, serverTimingHeader);
|
|
if (serverTimingHeader.IsEmpty()) {
|
|
return;
|
|
}
|
|
|
|
ServerTimingParser parser(serverTimingHeader);
|
|
parser.Parse();
|
|
|
|
nsTArray<nsCOMPtr<nsIServerTiming>> array = parser.TakeServerTimingHeaders();
|
|
aOutput.AppendElements(array);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetServerTiming(nsIArray **aServerTiming)
|
|
{
|
|
nsresult rv;
|
|
NS_ENSURE_ARG_POINTER(aServerTiming);
|
|
|
|
nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsTArray<nsCOMPtr<nsIServerTiming>> data;
|
|
rv = GetNativeServerTiming(data);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
for (const auto &entry : data) {
|
|
array->AppendElement(entry);
|
|
}
|
|
|
|
array.forget(aServerTiming);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
HttpBaseChannel::GetNativeServerTiming(nsTArray<nsCOMPtr<nsIServerTiming>>& aServerTiming)
|
|
{
|
|
aServerTiming.Clear();
|
|
|
|
bool isHTTPS = false;
|
|
if (NS_SUCCEEDED(mURI->SchemeIs("https", &isHTTPS)) && isHTTPS) {
|
|
ParseServerTimingHeader(mResponseHead, aServerTiming);
|
|
ParseServerTimingHeader(mResponseTrailers, aServerTiming);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
} // namespace net
|
|
} // namespace mozilla
|