diff options
Diffstat (limited to 'netwerk/protocol/http/HttpBaseChannel.cpp')
-rw-r--r-- | netwerk/protocol/http/HttpBaseChannel.cpp | 3715 |
1 files changed, 3715 insertions, 0 deletions
diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp new file mode 100644 index 000000000..66252b82f --- /dev/null +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -0,0 +1,3715 @@ +/* -*- 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 "nsHttpHandler.h" +#include "nsMimeTypes.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" + +#include "nsICachingChannel.h" +#include "nsIDOMDocument.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 "nsIScriptSecurityManager.h" +#include "nsIObserverService.h" +#include "nsProxyRelease.h" +#include "nsPIDOMWindow.h" +#include "nsIDocShell.h" +#include "nsINetworkInterceptController.h" +#include "mozilla/dom/Performance.h" +#include "mozIThirdPartyUtil.h" +#include "nsStreamUtils.h" +#include "nsContentSecurityManager.h" +#include "nsIChannelEventSink.h" +#include "nsILoadGroupChild.h" +#include "mozilla/ConsoleReportCollector.h" +#include "LoadInfo.h" +#include "nsNullPrincipal.h" +#include "nsISSLSocketControl.h" +#include "mozilla/Telemetry.h" +#include "nsIURL.h" +#include "nsIConsoleService.h" +#include "mozilla/BinarySearch.h" +#include "nsIHttpHeaderVisitor.h" +#include "nsIXULRuntime.h" +#include "nsICacheInfoChannel.h" +#include "nsIDOMWindowUtils.h" + +#include <algorithm> +#include "HttpBaseChannel.h" + +namespace mozilla { +namespace net { + +HttpBaseChannel::HttpBaseChannel() + : mStartPos(UINT64_MAX) + , mStatus(NS_OK) + , mLoadFlags(LOAD_NORMAL) + , mCaps(0) + , mClassOfService(0) + , mPriority(PRIORITY_NORMAL) + , mRedirectionLimit(gHttpHandler->RedirectionLimit()) + , mApplyConversion(true) + , mCanceled(false) + , mIsPending(false) + , mWasOpened(false) + , mRequestObserversCalled(false) + , mResponseHeadersModified(false) + , mAllowPipelining(true) + , mAllowSTS(true) + , mThirdPartyFlags(0) + , mUploadStreamHasHeaders(false) + , mInheritApplicationCache(true) + , mChooseApplicationCache(false) + , mLoadedFromApplicationCache(false) + , mChannelIsForDownload(false) + , mTracingEnabled(true) + , mTimingEnabled(false) + , mAllowSpdy(true) + , mAllowAltSvc(true) + , mBeConservative(false) + , mResponseTimeoutEnabled(true) + , mAllRedirectsSameOrigin(true) + , mAllRedirectsPassTimingAllowCheck(true) + , mResponseCouldBeSynthesized(false) + , mBlockAuthPrompt(false) + , mAllowStaleCacheContent(false) + , mSuspendCount(0) + , mInitialRwin(0) + , mProxyResolveFlags(0) + , mProxyURI(nullptr) + , mContentDispositionHint(UINT32_MAX) + , mHttpHandler(gHttpHandler) + , mReferrerPolicy(REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE) + , mRedirectCount(0) + , mForcePending(false) + , mCorsIncludeCredentials(false) + , mCorsMode(nsIHttpChannelInternal::CORS_MODE_NO_CORS) + , mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW) + , mFetchCacheMode(nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT) + , mOnStartRequestCalled(false) + , mOnStopRequestCalled(false) + , mAfterOnStartRequestBegun(false) + , mTransferSize(0) + , mDecodedBodySize(0) + , mEncodedBodySize(0) + , mContentWindowId(0) + , mRequireCORSPreflight(false) + , mReportCollector(new ConsoleReportCollector()) + , mForceMainDocumentChannel(false) +{ + LOG(("Creating HttpBaseChannel @%x\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; + mRequestContextID.Clear(); +} + +HttpBaseChannel::~HttpBaseChannel() +{ + LOG(("Destroying HttpBaseChannel @%x\n", this)); + + NS_ReleaseOnMainThread(mLoadInfo.forget()); + + // Make sure we don't leak + CleanRedirectCacheChainIfNecessary(); +} + +nsresult +HttpBaseChannel::Init(nsIURI *aURI, + uint32_t aCaps, + nsProxyInfo *aProxyInfo, + uint32_t aProxyResolveFlags, + nsIURI *aProxyURI, + const nsID& aChannelId) +{ + LOG(("HttpBaseChannel::Init [this=%p]\n", this)); + + NS_PRECONDITION(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_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) +{ + bool synthesized = false; + nsresult rv = GetResponseSynthesized(&synthesized); + NS_ENSURE_SUCCESS(rv, rv); + + // If this channel is marked as awaiting a synthesized response, + // modifying certain load flags can interfere with the implementation + // of the network interception logic. This takes care of a couple + // known cases that attempt to mark channels as anonymous due + // to cross-origin redirects; since the response is entirely synthesized + // this is an unnecessary precaution. + // This should be removed when bug 1201683 is fixed. + if (synthesized && aLoadFlags != mLoadFlags) { + aLoadFlags &= ~LOAD_ANONYMOUS; + } + + mLoadFlags = aLoadFlags; + mForceMainDocumentChannel = (aLoadFlags & LOAD_DOCUMENT_URI); + 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::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; + + *aContentLength = mResponseHead->ContentLength(); + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetContentLength(int64_t value) +{ + NS_NOTYETIMPLEMENTED("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; + + // 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. + nsAutoCString contentType; + if (contentTypeArg.IsEmpty()) { + method = NS_LITERAL_CSTRING("POST"); + hasHeaders = true; + contentType.SetIsVoid(true); + } else { + method = NS_LITERAL_CSTRING("PUT"); + hasHeaders = false; + contentType = contentTypeArg; + } + 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) { + // Called on the STS thread by NS_AsyncCopy + auto channel = static_cast<HttpBaseChannel*>(aClosure); + nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod<nsresult>( + channel, &HttpBaseChannel::EnsureUploadStreamIsCloneableComplete, aStatus); + NS_DispatchToMainThread(runnable.forget()); +} + +} // 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, 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::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(nsIInputStream** aClonedStream) +{ + 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); + + 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); + + if (aContentLength < 0 && !aStreamHasHeaders) { + nsresult rv = aStream->Available(reinterpret_cast<uint64_t*>(&aContentLength)); + if (NS_FAILED(rv) || aContentLength < 0) { + NS_ERROR("unable to determine content length"); + return NS_ERROR_FAILURE; + } + } + + nsresult rv = SetRequestMethod(aMethod); + NS_ENSURE_SUCCESS(rv, rv); + + if (!aStreamHasHeaders) { + // SetRequestHeader propagates headers to chrome if HttpChannelChild + nsAutoCString contentLengthStr; + contentLengthStr.AppendInt(aContentLength); + SetRequestHeader(NS_LITERAL_CSTRING("Content-Length"), contentLengthStr, + false); + if (!aContentType.IsVoid()) { + if (aContentType.IsEmpty()) { + SetEmptyRequestHeader(NS_LITERAL_CSTRING("Content-Type")); + } else { + SetRequestHeader(NS_LITERAL_CSTRING("Content-Type"), aContentType, + false); + } + } + } + + mUploadStreamHasHeaders = aStreamHasHeaders; + mUploadStream = aStream; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::GetUploadStreamHasHeaders(bool *hasHeaders) +{ + NS_ENSURE_ARG(hasHeaders); + + *hasHeaders = mUploadStreamHasHeaders; + return NS_OK; +} + +//----------------------------------------------------------------------------- +// 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() {} + nsCOMPtr<nsIStreamListener> mNext; + HttpBaseChannel *mChannel; + +public: + InterceptFailedOnStop(nsIStreamListener *arg, HttpBaseChannel *chan) + : mNext(arg) + , mChannel(chan) {} + NS_DECL_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 %x", mChannel, 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.Equals("gzip") || from.Equals("x-gzip")) { + mode = 1; + } else if (from.Equals("deflate") || from.Equals("x-deflate")) { + mode = 2; + } else if (from.Equals("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; + 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::~nsContentEncodings() +{ +} + +//----------------------------------------------------------------------------- +// 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(nsACString& aChannelId) +{ + char id[NSID_LENGTH]; + mChannelId.ToProvidedString(id); + aChannelId.AssignASCII(id); + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetChannelId(const nsACString& aChannelId) +{ + nsID newId; + nsAutoCString idStr(aChannelId); + if (newId.Parse(idStr.get())) { + mChannelId = newId; + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +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 = do_GetInterface(topWindow); + if (windowUtils) { + windowUtils->GetCurrentInnerWindowID(&mContentWindowId); + } + } + } + *aWindowId = mContentWindowId; + return NS_OK; +} + +NS_IMETHODIMP HttpBaseChannel::SetTopLevelContentWindowId(uint64_t aWindowId) +{ + mContentWindowId = aWindowId; + 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::GetNetworkInterfaceId(nsACString& aNetworkInterfaceId) +{ + aNetworkInterfaceId = mNetworkInterfaceId; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetNetworkInterfaceId(const nsACString& aNetworkInterfaceId) +{ + ENSURE_CALLED_BEFORE_CONNECT(); + mNetworkInterfaceId = aNetworkInterfaceId; + 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) +{ + return SetReferrerWithPolicy(referrer, REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE); +} + +NS_IMETHODIMP +HttpBaseChannel::GetReferrerPolicy(uint32_t *referrerPolicy) +{ + NS_ENSURE_ARG_POINTER(referrerPolicy); + *referrerPolicy = mReferrerPolicy; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetReferrerWithPolicy(nsIURI *referrer, + uint32_t referrerPolicy) +{ + ENSURE_CALLED_BEFORE_CONNECT(); + + // clear existing referrer, if any + mReferrer = nullptr; + nsresult rv = mRequestHead.ClearHeader(nsHttp::Referer); + if(NS_FAILED(rv)) { + return rv; + } + mReferrerPolicy = REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE; + + if (!referrer) { + return NS_OK; + } + + // Don't send referrer at all when the meta referrer setting is "no-referrer" + if (referrerPolicy == REFERRER_POLICY_NO_REFERRER) { + 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(); + + // 0: full URI + // 1: scheme+host+port+path + // 2: scheme+host+port + int userReferrerTrimmingPolicy = gHttpHandler->ReferrerTrimmingPolicy(); + + // 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->GetPath(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; + + // Get charset of the original URI so we can pass it to our fixed up URI. + nsAutoCString charset; + referrer->GetOriginCharset(charset); + + // Replace |referrer| with a URI without wyciwyg://123/. + rv = NS_NewURI(getter_AddRefs(referrerGrip), + Substring(path, slashIndex + 1, pathLength - slashIndex - 1), + charset.get()); + if (NS_FAILED(rv)) return rv; + + referrer = referrerGrip.get(); + } + + // + // block referrer if not on our white list... + // + static const char *const referrerWhiteList[] = { + "http", + "https", + "ftp", + nullptr + }; + match = false; + const char *const *scheme = referrerWhiteList; + for (; *scheme && !match; ++scheme) { + rv = referrer->SchemeIs(*scheme, &match); + if (NS_FAILED(rv)) return rv; + } + if (!match) 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 (referrerPolicy != REFERRER_POLICY_UNSAFE_URL && + referrerPolicy != REFERRER_POLICY_ORIGIN_WHEN_XORIGIN && + referrerPolicy != REFERRER_POLICY_ORIGIN) { + + // in other referrer policies, https->http is not allowed... + if (!match) return NS_OK; + } + } + + // for cross-origin-based referrer changes (not just host-based), figure out + // if the referrer is being sent cross-origin. + nsCOMPtr<nsIURI> triggeringURI; + bool isCrossOrigin = true; + if (mLoadInfo) { + nsCOMPtr<nsIPrincipal> triggeringPrincipal = mLoadInfo->TriggeringPrincipal(); + if (triggeringPrincipal) { + triggeringPrincipal->GetURI(getter_AddRefs(triggeringURI)); + } + } + if (triggeringURI) { + if (LOG_ENABLED()) { + nsAutoCString triggeringURISpec; + rv = triggeringURI->GetAsciiSpec(triggeringURISpec); + if (!NS_FAILED(rv)) { + LOG(("triggeringURI=%s\n", triggeringURISpec.get())); + } + } + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + rv = ssm->CheckSameOriginURI(triggeringURI, mURI, false); + isCrossOrigin = NS_FAILED(rv); + } else { + LOG(("no triggering principal available via loadInfo, assuming load is cross-origin")); + } + + // Don't send referrer when the request is cross-origin and policy is "same-origin". + if (isCrossOrigin && referrerPolicy == REFERRER_POLICY_SAME_ORIGIN) { + mReferrerPolicy = REFERRER_POLICY_SAME_ORIGIN; + 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 + // + // Use CloneIgnoringRef to strip away any fragment per RFC 2616 section 14.36 + // and Referrer Policy section 6.3.5. + rv = referrer->CloneIgnoringRef(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; + + // 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 = mURI->CloneIgnoringRef(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 = clone->SetUserPass(EmptyCString()); + if (NS_FAILED(rv)) return rv; + + nsAutoCString spec; + + // Apply the user cross-origin trimming policy if it's more + // restrictive than the general one. + if (isCrossOrigin) { + int userReferrerXOriginTrimmingPolicy = + gHttpHandler->ReferrerXOriginTrimmingPolicy(); + userReferrerTrimmingPolicy = + std::max(userReferrerTrimmingPolicy, userReferrerXOriginTrimmingPolicy); + } + + // site-specified referrer trimming may affect the trim level + // "unsafe-url" behaves like "origin" (send referrer in the same situations) but + // "unsafe-url" sends the whole referrer and origin removes the path. + // "origin-when-cross-origin" trims the referrer only when the request is + // cross-origin. + // "Strict" request from https->http case was bailed out, so here: + // "strict-origin" behaves the same as "origin". + // "strict-origin-when-cross-origin" behaves the same as "origin-when-cross-origin" + if (referrerPolicy == REFERRER_POLICY_ORIGIN || + referrerPolicy == REFERRER_POLICY_STRICT_ORIGIN || + (isCrossOrigin && (referrerPolicy == REFERRER_POLICY_ORIGIN_WHEN_XORIGIN || + referrerPolicy == REFERRER_POLICY_STRICT_ORIGIN_WHEN_XORIGIN))) { + // We can override the user trimming preference because "origin" + // (network.http.referer.trimmingPolicy = 2) is the strictest + // trimming policy that users can specify. + userReferrerTrimmingPolicy = 2; + } + + // 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 = url->SetQuery(EmptyCString()); + if (NS_FAILED(rv)) return rv; + rv = url->SetRef(EmptyCString()); + 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 = clone->SetPath(EmptyCString()); + 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; + mReferrerPolicy = referrerPolicy; + 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; + } + + nsHttpAtom atom = nsHttp::ResolveAtom(flatHeader.get()); + if (!atom) { + NS_WARNING("failed to resolve atom"); + return NS_ERROR_NOT_AVAILABLE; + } + + return mRequestHead.SetHeader(atom, 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; + } + + nsHttpAtom atom = nsHttp::ResolveAtom(flatHeader.get()); + if (!atom) { + NS_WARNING("failed to resolve atom"); + return NS_ERROR_NOT_AVAILABLE; + } + + return mRequestHead.SetEmptyHeader(atom); +} + +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(atom, 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 = mAllowPipelining; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetAllowPipelining(bool value) +{ + ENSURE_CALLED_BEFORE_CONNECT(); + + mAllowPipelining = value; + 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) +{ + // 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; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::GetRequestContextID(nsID *aRCID) +{ + NS_ENSURE_ARG_POINTER(aRCID); + *aRCID = mRequestContextID; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetRequestContextID(const nsID aRCID) +{ + mRequestContextID = aRCID; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::GetIsMainDocumentChannel(bool* aValue) +{ + NS_ENSURE_ARG_POINTER(aValue); + *aValue = mForceMainDocumentChannel || (mLoadFlags & LOAD_DOCUMENT_URI); + 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) { + uint32_t version = mResponseHead->Version(); + aProtocolVersion.Assign(nsHttp::GetProtocolVersion(version)); + return NS_OK; + } + + return NS_ERROR_NOT_AVAILABLE; +} + + +//----------------------------------------------------------------------------- +// HttpBaseChannel::nsIHttpChannelInternal +//----------------------------------------------------------------------------- + +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 = do_GetService(THIRDPARTYUTIL_CONTRACTID); + 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) +{ + nsHttpVersion version = mRequestHead.Version(); + + if (major) { *major = version / 10; } + if (minor) { *minor = 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; + } + + nsHttpVersion version = mResponseHead->Version(); + + if (major) { *major = version / 10; } + if (minor) { *minor = version % 10; } + + return NS_OK; +} + +void +HttpBaseChannel::NotifySetCookie(char const *aCookie) +{ + nsCOMPtr<nsIObserverService> obs = services::GetObserverService(); + if (obs) { + nsAutoString cookie; + CopyASCIItoUTF16(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; + 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) +{ + aMessages.Clear(); + aMessages.SwapElements(mSecurityConsoleMessages); + 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) +{ + nsresult rv; + nsCOMPtr<nsISecurityConsoleMessage> message = + do_CreateInstance(NS_SECURITY_CONSOLE_MESSAGE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + message->SetTag(aMessageTag); + message->SetCategory(aMessageCategory); + mSecurityConsoleMessages.AppendElement(message); + + 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; + } + + uint32_t innerWindowID = loadInfo->GetInnerWindowID(); + + nsXPIDLString errorText; + rv = nsContentUtils::GetLocalizedString( + nsContentUtils::eSECURITY_PROPERTIES, + NS_ConvertUTF16toUTF8(aMessageTag).get(), + errorText); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString spec; + if (mURI) { + spec = mURI->GetSpecOrDefault(); + } + + nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); + error->InitWithWindowID(errorText, NS_ConvertUTF8toUTF16(spec), + 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::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; + mResponseHead->GetLastModifiedValue(&lastMod); + *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; +} + +NS_IMETHODIMP +HttpBaseChannel::GetFetchCacheMode(uint32_t* aFetchCacheMode) +{ + NS_ENSURE_ARG_POINTER(aFetchCacheMode); + + // If the fetch cache mode is overriden, then use it directly. + if (mFetchCacheMode != nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT) { + *aFetchCacheMode = mFetchCacheMode; + return NS_OK; + } + + // Otherwise try to guess an appropriate cache mode from the load flags. + if (mLoadFlags & (INHIBIT_CACHING | LOAD_BYPASS_CACHE)) { + *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE; + } else if (mLoadFlags & LOAD_BYPASS_CACHE) { + *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD; + } else if (mLoadFlags & VALIDATE_ALWAYS) { + *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE; + } else if (mLoadFlags & (LOAD_FROM_CACHE | nsICachingChannel::LOAD_ONLY_FROM_CACHE)) { + *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED; + } else if (mLoadFlags & LOAD_FROM_CACHE) { + *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE; + } else { + *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT; + } + + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetFetchCacheMode(uint32_t aFetchCacheMode) +{ + ENSURE_CALLED_BEFORE_CONNECT(); + MOZ_ASSERT(mFetchCacheMode == nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT, + "SetFetchCacheMode() should only be called once per channel"); + + mFetchCacheMode = aFetchCacheMode; + + // Now, set the load flags that implement each cache mode. + switch (mFetchCacheMode) { + 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. + 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. + mLoadFlags |= LOAD_BYPASS_CACHE; + break; + case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE: + // no-cache means always validate what's in the cache. + mLoadFlags |= VALIDATE_ALWAYS; + break; + case nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE: + // force-cache means don't validate unless if the response would vary. + mLoadFlags |= LOAD_FROM_CACHE; + 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. + mLoadFlags |= LOAD_FROM_CACHE | nsICachingChannel::LOAD_ONLY_FROM_CACHE; + break; + } + + 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; +} + +mozilla::net::nsHttpChannel* +HttpBaseChannel::QueryHttpChannelImpl(void) +{ + return nullptr; +} + +//----------------------------------------------------------------------------- +// 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; + 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(); + mResponseHead->GetHeader(nsHttp::Last_Modified, lastmod); + 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::FlushConsoleReports(nsIDocument* aDocument, + ReportAction aAction) +{ + mReportCollector->FlushConsoleReports(aDocument, aAction); +} + +void +HttpBaseChannel::FlushConsoleReports(nsIConsoleReportCollector* aCollector) +{ + mReportCollector->FlushConsoleReports(aCollector); +} + +void +HttpBaseChannel::FlushReportsByWindowId(uint64_t aWindowId, + ReportAction aAction) +{ + mReportCollector->FlushReportsByWindowId(aWindowId, aAction); +} + +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; +} + +bool +HttpBaseChannel::BypassServiceWorker() const +{ + return mLoadFlags & LOAD_BYPASS_SERVICE_WORKER; +} + +bool +HttpBaseChannel::ShouldIntercept(nsIURI* aURI) +{ + nsCOMPtr<nsINetworkInterceptController> controller; + GetCallback(controller); + bool shouldIntercept = false; + if (controller && !BypassServiceWorker() && mLoadInfo) { + nsresult rv = controller->ShouldPrepareForIntercept(aURI ? aURI : mURI.get(), + nsContentUtils::IsNonSubresourceRequest(this), + &shouldIntercept); + if (NS_FAILED(rv)) { + return false; + } + } + return shouldIntercept; +} + +#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; + } + + DocShellOriginAttributes docShellAttrs; + loadContext->GetOriginAttributes(docShellAttrs); + MOZ_ASSERT(mLoadInfo->GetOriginAttributes().mPrivateBrowsingId == docShellAttrs.mPrivateBrowsingId, + "PrivateBrowsingId values are not the same between LoadInfo and LoadContext."); +} +#endif + +//----------------------------------------------------------------------------- +// 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() +{ + 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; + } + + // 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() && mLoadInfo) { + nsCOMPtr<nsIDOMDocument> dommyDoc; + mLoadInfo->GetLoadingDocument(getter_AddRefs(dommyDoc)); + nsCOMPtr<nsIDocument> doc = do_QueryInterface(dommyDoc); + FlushConsoleReports(doc); + } +} + +void +HttpBaseChannel::AddCookiesToRequest() +{ + if (mLoadFlags & LOAD_ANONYMOUS) { + return; + } + + bool useCookieService = + (XRE_IsParentProcess()); + nsXPIDLCString 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); +} + +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; +} + +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 SetupReplacementChannelHeaderVisitor final : public nsIHttpHeaderVisitor +{ +public: + NS_DECL_ISUPPORTS + + explicit SetupReplacementChannelHeaderVisitor(nsIHttpChannel *aChannel) + : mChannel(aChannel) + { + } + + NS_IMETHOD VisitHeader(const nsACString& aHeader, + const nsACString& aValue) override + { + nsHttpAtom atom = nsHttp::ResolveAtom(aHeader); + if (!IsHeaderBlacklistedForRedirectCopy(atom)) { + mChannel->SetRequestHeader(aHeader, aValue, false); + } + return NS_OK; + } +private: + ~SetupReplacementChannelHeaderVisitor() + { + } + + nsCOMPtr<nsIHttpChannel> mChannel; +}; + +NS_IMPL_ISUPPORTS(SetupReplacementChannelHeaderVisitor, nsIHttpHeaderVisitor) + +nsresult +HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, + nsIChannel *newChannel, + bool preserveMethod, + uint32_t redirectFlags) +{ + LOG(("HttpBaseChannel::SetupReplacementChannel " + "[this=%p newChannel=%p preserveMethod=%d]", + this, newChannel, preserveMethod)); + + 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; + nsresult 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); + + // Try to preserve the privacy bit if it has been overridden + if (mPrivateBrowsingOverriden) { + nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel = + do_QueryInterface(newChannel); + if (newPBChannel) { + newPBChannel->SetPrivate(mPrivateBrowsing); + } + } + + // make a copy of the loadinfo, append to the redirectchain + // and set it on the new channel + if (mLoadInfo) { + nsCOMPtr<nsILoadInfo> newLoadInfo = + static_cast<mozilla::LoadInfo*>(mLoadInfo.get())->Clone(); + + nsContentPolicyType contentPolicyType = mLoadInfo->GetExternalContentPolicyType(); + if (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT || + contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT) { + nsCOMPtr<nsIPrincipal> nullPrincipalToInherit = nsNullPrincipal::Create(); + 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); + DocShellOriginAttributes docShellAttrs; + if (loadContext) { + loadContext->GetOriginAttributes(docShellAttrs); + } + MOZ_ASSERT(docShellAttrs.mFirstPartyDomain.IsEmpty(), + "top-level docshell shouldn't have firstPartyDomain attribute."); + + NeckoOriginAttributes attrs = newLoadInfo->GetOriginAttributes(); + + MOZ_ASSERT(docShellAttrs.mAppId == attrs.mAppId, + "docshell and necko should have the same appId attribute."); + 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.InheritFromDocShellToNecko(docShellAttrs, true, newURI); + newLoadInfo->SetOriginAttributes(attrs); + } + + bool isInternalRedirect = + (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL | + nsIChannelEventSink::REDIRECT_STS_UPGRADE)); + newLoadInfo->AppendRedirectedPrincipal(GetURIPrincipal(), isInternalRedirect); + newChannel->SetLoadInfo(newLoadInfo); + } + else { + // the newChannel was created with a dummy loadInfo, we should clear + // it in case the original channel does not have a loadInfo + newChannel->SetLoadInfo(nullptr); + } + + 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 (mRequireCORSPreflight && httpInternal) { + 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); + if (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; + 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); + httpChannel->SetRequestMethod(method); + } + // convey the referrer if one was used for this channel to the next one + if (mReferrer) + httpChannel->SetReferrerWithPolicy(mReferrer, mReferrerPolicy); + // convey the mAllowPipelining and mAllowSTS flags + httpChannel->SetAllowPipelining(mAllowPipelining); + httpChannel->SetAllowSTS(mAllowSTS); + // convey the new redirection limit + // make sure we don't underflow + uint32_t redirectionLimit = mRedirectionLimit + ? mRedirectionLimit - 1 + : 0; + httpChannel->SetRedirectionLimit(redirectionLimit); + + // convey the Accept header value + { + nsAutoCString oldAcceptValue; + nsresult hasHeader = mRequestHead.GetHeader(nsHttp::Accept, oldAcceptValue); + if (NS_SUCCEEDED(hasHeader)) { + httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"), + oldAcceptValue, + false); + } + } + + // share the request context - see bug 1236650 + httpChannel->SetRequestContextID(mRequestContextID); + + if (httpInternal) { + // Convey third party cookie, conservative, and spdy flags. + httpInternal->SetThirdPartyFlags(mThirdPartyFlags); + httpInternal->SetAllowSpdy(mAllowSpdy); + httpInternal->SetAllowAltSvc(mAllowAltSvc); + httpInternal->SetBeConservative(mBeConservative); + + RefPtr<nsHttpChannel> realChannel; + CallQueryInterface(newChannel, realChannel.StartAssignment()); + if (realChannel) { + realChannel->SetTopWindowURI(mTopWindowURI); + } + + // 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)) + httpInternal->SetDocumentURI(newURI); + else + httpInternal->SetDocumentURI(mDocumentURI); + + // 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)); + httpInternal->SetCacheKeysRedirectChain(mRedirectedCachekeys.forget()); + } + + // Preserve CORS mode flag. + httpInternal->SetCorsMode(mCorsMode); + + // Preserve Redirect mode flag. + httpInternal->SetRedirectMode(mRedirectMode); + + // Preserve Cache mode flag. + httpInternal->SetFetchCacheMode(mFetchCacheMode); + + // Preserve Integrity metadata. + httpInternal->SetIntegrityMetadata(mIntegrityMetadata); + } + + // 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); + newTimedChannel->SetRedirectCount(mRedirectCount + 1); + + // 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()) { + TimeStamp asyncOpen; + oldTimedChannel->GetAsyncOpen(&asyncOpen); + newTimedChannel->SetRedirectStart(asyncOpen); + } + else { + newTimedChannel->SetRedirectStart(mRedirectStartTimeStamp); + } + + // The RedirectEnd timestamp is equal to the previous channel response end. + TimeStamp prevResponseEnd; + oldTimedChannel->GetResponseEnd(&prevResponseEnd); + newTimedChannel->SetRedirectEnd(prevResponseEnd); + + 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)); + } + } + + // 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 SetupReplacementChannelHeaderVisitor(httpChannel); + mRequestHead.VisitHeaders(visitor); + } + + // 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::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::GetAsyncOpen(TimeStamp* _retval) { + *_retval = mAsyncOpenTime; + 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(uint16_t *aRedirectCount) +{ + *aRedirectCount = mRedirectCount; + return NS_OK; +} + +NS_IMETHODIMP +HttpBaseChannel::SetRedirectCount(uint16_t aRedirectCount) +{ + mRedirectCount = 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; + } + + if (headerValue == "*") { + *_retval = true; + return NS_OK; + } + + nsAutoCString origin; + nsContentUtils::GetASCIIOrigin(aOrigin, origin); + + if (headerValue == origin) { + *_retval = true; + return NS_OK; + } + + *_retval = false; + 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::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(DomainLookupStart) +IMPL_TIMING_ATTR(DomainLookupEnd) +IMPL_TIMING_ATTR(ConnectStart) +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::Performance* +HttpBaseChannel::GetPerformance() +{ + // 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_IsParentProcess() && BrowserTabsRemoteAutostart()) { + return nullptr; + } + + if (!mLoadInfo) { + return nullptr; + } + + // We don't need to report the resource timing entry for a TYPE_DOCUMENT load. + if (mLoadInfo->GetExternalContentPolicyType() == nsIContentPolicyBase::TYPE_DOCUMENT) { + return nullptr; + } + + nsCOMPtr<nsIDOMDocument> domDocument; + mLoadInfo->GetLoadingDocument(getter_AddRefs(domDocument)); + if (!domDocument) { + return nullptr; + } + + nsCOMPtr<nsIDocument> loadingDocument = do_QueryInterface(domDocument); + 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* docPerformance = innerWindow->GetPerformance(); + if (!docPerformance) { + return nullptr; + } + + return docPerformance; +} + +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() +{ + nsID nullID; + nullID.Clear(); + if (!mRequestContextID.Equals(nullID)) { + // Already have a request context ID, no need to do the rest of this work + 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 the transaction + rootLoadGroup->GetRequestContextID(&mRequestContextID); + return true; +} + +void +HttpBaseChannel::SetCorsPreflightParameters(const nsTArray<nsCString>& aUnsafeHeaders) +{ + MOZ_RELEASE_ASSERT(!mRequestObserversCalled); + + mRequireCORSPreflight = true; + mUnsafeHeaders = aUnsafeHeaders; +} + +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; +} + +} // namespace net +} // namespace mozilla |