summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/InterceptedChannel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/protocol/http/InterceptedChannel.cpp')
-rw-r--r--netwerk/protocol/http/InterceptedChannel.cpp540
1 files changed, 540 insertions, 0 deletions
diff --git a/netwerk/protocol/http/InterceptedChannel.cpp b/netwerk/protocol/http/InterceptedChannel.cpp
new file mode 100644
index 000000000..9e38e2734
--- /dev/null
+++ b/netwerk/protocol/http/InterceptedChannel.cpp
@@ -0,0 +1,540 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: -*- */
+/* vim:set expandtab ts=2 sw=2 sts=2 cin: */
+/* 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/. */
+
+#include "HttpLog.h"
+
+#include "InterceptedChannel.h"
+#include "nsInputStreamPump.h"
+#include "nsIPipe.h"
+#include "nsIStreamListener.h"
+#include "nsHttpChannel.h"
+#include "HttpChannelChild.h"
+#include "nsHttpResponseHead.h"
+#include "nsNetUtil.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/dom/ChannelInfo.h"
+#include "nsIChannelEventSink.h"
+
+namespace mozilla {
+namespace net {
+
+extern bool
+WillRedirect(const nsHttpResponseHead * response);
+
+extern nsresult
+DoUpdateExpirationTime(nsHttpChannel* aSelf,
+ nsICacheEntry* aCacheEntry,
+ nsHttpResponseHead* aResponseHead,
+ uint32_t& aExpirationTime);
+extern nsresult
+DoAddCacheEntryHeaders(nsHttpChannel *self,
+ nsICacheEntry *entry,
+ nsHttpRequestHead *requestHead,
+ nsHttpResponseHead *responseHead,
+ nsISupports *securityInfo);
+
+NS_IMPL_ISUPPORTS(InterceptedChannelBase, nsIInterceptedChannel)
+
+InterceptedChannelBase::InterceptedChannelBase(nsINetworkInterceptController* aController)
+: mController(aController)
+, mReportCollector(new ConsoleReportCollector())
+, mClosed(false)
+{
+}
+
+InterceptedChannelBase::~InterceptedChannelBase()
+{
+}
+
+NS_IMETHODIMP
+InterceptedChannelBase::GetResponseBody(nsIOutputStream** aStream)
+{
+ NS_IF_ADDREF(*aStream = mResponseBody);
+ return NS_OK;
+}
+
+void
+InterceptedChannelBase::EnsureSynthesizedResponse()
+{
+ if (mSynthesizedResponseHead.isNothing()) {
+ mSynthesizedResponseHead.emplace(new nsHttpResponseHead());
+ }
+}
+
+void
+InterceptedChannelBase::DoNotifyController()
+{
+ nsresult rv = NS_OK;
+
+ if (NS_WARN_IF(!mController)) {
+ rv = ResetInterception();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Failed to resume intercepted network request");
+ return;
+ }
+
+ rv = mController->ChannelIntercepted(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ rv = ResetInterception();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "Failed to resume intercepted network request");
+ }
+ mController = nullptr;
+}
+
+nsresult
+InterceptedChannelBase::DoSynthesizeStatus(uint16_t aStatus, const nsACString& aReason)
+{
+ EnsureSynthesizedResponse();
+
+ // Always assume HTTP 1.1 for synthesized responses.
+ nsAutoCString statusLine;
+ statusLine.AppendLiteral("HTTP/1.1 ");
+ statusLine.AppendInt(aStatus);
+ statusLine.AppendLiteral(" ");
+ statusLine.Append(aReason);
+
+ (*mSynthesizedResponseHead)->ParseStatusLine(statusLine);
+ return NS_OK;
+}
+
+nsresult
+InterceptedChannelBase::DoSynthesizeHeader(const nsACString& aName, const nsACString& aValue)
+{
+ EnsureSynthesizedResponse();
+
+ nsAutoCString header = aName + NS_LITERAL_CSTRING(": ") + aValue;
+ // Overwrite any existing header.
+ nsresult rv = (*mSynthesizedResponseHead)->ParseHeaderLine(header);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelBase::GetConsoleReportCollector(nsIConsoleReportCollector** aCollectorOut)
+{
+ MOZ_ASSERT(aCollectorOut);
+ nsCOMPtr<nsIConsoleReportCollector> ref = mReportCollector;
+ ref.forget(aCollectorOut);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelBase::SetReleaseHandle(nsISupports* aHandle)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mReleaseHandle);
+ MOZ_ASSERT(aHandle);
+
+ // We need to keep it and mChannel alive until destructor clear it up.
+ mReleaseHandle = aHandle;
+ return NS_OK;
+}
+
+/* static */
+already_AddRefed<nsIURI>
+InterceptedChannelBase::SecureUpgradeChannelURI(nsIChannel* aChannel)
+{
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ nsCOMPtr<nsIURI> upgradedURI;
+ rv = NS_GetSecureUpgradedURI(uri, getter_AddRefs(upgradedURI));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+
+ return upgradedURI.forget();
+}
+
+InterceptedChannelChrome::InterceptedChannelChrome(nsHttpChannel* aChannel,
+ nsINetworkInterceptController* aController,
+ nsICacheEntry* aEntry)
+: InterceptedChannelBase(aController)
+, mChannel(aChannel)
+, mSynthesizedCacheEntry(aEntry)
+{
+ nsresult rv = mChannel->GetApplyConversion(&mOldApplyConversion);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ mOldApplyConversion = false;
+ }
+}
+
+void
+InterceptedChannelChrome::NotifyController()
+{
+ // Intercepted responses should already be decoded.
+ mChannel->SetApplyConversion(false);
+
+ nsresult rv = mSynthesizedCacheEntry->OpenOutputStream(0, getter_AddRefs(mResponseBody));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ DoNotifyController();
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::GetChannel(nsIChannel** aChannel)
+{
+ NS_IF_ADDREF(*aChannel = mChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::ResetInterception()
+{
+ if (mClosed) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ mSynthesizedCacheEntry->AsyncDoom(nullptr);
+ mSynthesizedCacheEntry = nullptr;
+
+ mChannel->SetApplyConversion(mOldApplyConversion);
+
+ nsCOMPtr<nsIURI> uri;
+ mChannel->GetURI(getter_AddRefs(uri));
+
+ nsresult rv = mChannel->StartRedirectChannelToURI(uri, nsIChannelEventSink::REDIRECT_INTERNAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mResponseBody->Close();
+ mResponseBody = nullptr;
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::SynthesizeStatus(uint16_t aStatus, const nsACString& aReason)
+{
+ if (!mSynthesizedCacheEntry) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return DoSynthesizeStatus(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::SynthesizeHeader(const nsACString& aName, const nsACString& aValue)
+{
+ if (!mSynthesizedCacheEntry) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return DoSynthesizeHeader(aName, aValue);
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::FinishSynthesizedResponse(const nsACString& aFinalURLSpec)
+{
+ if (mClosed) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Make sure the cache entry's output stream is always closed. If the
+ // channel was intercepted with a null-body response then its possible
+ // the synthesis completed without a stream copy operation.
+ mResponseBody->Close();
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ EnsureSynthesizedResponse();
+
+ // If the synthesized response is a redirect, then we want to respect
+ // the encoding of whatever is loaded as a result.
+ if (WillRedirect(mSynthesizedResponseHead.ref())) {
+ nsresult rv = mChannel->SetApplyConversion(mOldApplyConversion);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mChannel->MarkIntercepted();
+
+ // First we ensure the appropriate metadata is set on the synthesized cache entry
+ // (i.e. the flattened response head)
+
+ nsCOMPtr<nsISupports> securityInfo;
+ nsresult rv = mChannel->GetSecurityInfo(getter_AddRefs(securityInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t expirationTime = 0;
+ rv = DoUpdateExpirationTime(mChannel, mSynthesizedCacheEntry,
+ mSynthesizedResponseHead.ref(),
+ expirationTime);
+
+ rv = DoAddCacheEntryHeaders(mChannel, mSynthesizedCacheEntry,
+ mChannel->GetRequestHead(),
+ mSynthesizedResponseHead.ref(), securityInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIURI> originalURI;
+ mChannel->GetURI(getter_AddRefs(originalURI));
+
+ nsCOMPtr<nsIURI> responseURI;
+ if (!aFinalURLSpec.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(responseURI), aFinalURLSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ responseURI = originalURI;
+ }
+
+ bool equal = false;
+ originalURI->Equals(responseURI, &equal);
+ if (!equal) {
+ rv =
+ mChannel->StartRedirectChannelToURI(responseURI, nsIChannelEventSink::REDIRECT_INTERNAL);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ bool usingSSL = false;
+ responseURI->SchemeIs("https", &usingSSL);
+
+ // Then we open a real cache entry to read the synthesized response from.
+ rv = mChannel->OpenCacheEntry(usingSSL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSynthesizedCacheEntry = nullptr;
+
+ if (!mChannel->AwaitingCacheCallbacks()) {
+ rv = mChannel->ContinueConnect();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::Cancel(nsresult aStatus)
+{
+ MOZ_ASSERT(NS_FAILED(aStatus));
+
+ if (mClosed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ // we need to use AsyncAbort instead of Cancel since there's no active pump
+ // to cancel which will provide OnStart/OnStopRequest to the channel.
+ nsresult rv = mChannel->AsyncAbort(aStatus);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::SetChannelInfo(dom::ChannelInfo* aChannelInfo)
+{
+ if (mClosed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return aChannelInfo->ResurrectInfoOnChannel(mChannel);
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::GetInternalContentPolicyType(nsContentPolicyType* aPolicyType)
+{
+ NS_ENSURE_ARG(aPolicyType);
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ nsresult rv = mChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aPolicyType = loadInfo->InternalContentPolicyType();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelChrome::GetSecureUpgradedChannelURI(nsIURI** aURI)
+{
+ return mChannel->GetURI(aURI);
+}
+
+InterceptedChannelContent::InterceptedChannelContent(HttpChannelChild* aChannel,
+ nsINetworkInterceptController* aController,
+ InterceptStreamListener* aListener,
+ bool aSecureUpgrade)
+: InterceptedChannelBase(aController)
+, mChannel(aChannel)
+, mStreamListener(aListener)
+, mSecureUpgrade(aSecureUpgrade)
+{
+}
+
+void
+InterceptedChannelContent::NotifyController()
+{
+ nsresult rv = NS_NewPipe(getter_AddRefs(mSynthesizedInput),
+ getter_AddRefs(mResponseBody),
+ 0, UINT32_MAX, true, true);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ DoNotifyController();
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::GetChannel(nsIChannel** aChannel)
+{
+ NS_IF_ADDREF(*aChannel = mChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::ResetInterception()
+{
+ if (mClosed) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ mResponseBody->Close();
+ mResponseBody = nullptr;
+ mSynthesizedInput = nullptr;
+
+ mChannel->ResetInterception();
+
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::SynthesizeStatus(uint16_t aStatus, const nsACString& aReason)
+{
+ if (!mResponseBody) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return DoSynthesizeStatus(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::SynthesizeHeader(const nsACString& aName, const nsACString& aValue)
+{
+ if (!mResponseBody) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return DoSynthesizeHeader(aName, aValue);
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::FinishSynthesizedResponse(const nsACString& aFinalURLSpec)
+{
+ if (NS_WARN_IF(mClosed)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Make sure the body output stream is always closed. If the channel was
+ // intercepted with a null-body response then its possible the synthesis
+ // completed without a stream copy operation.
+ mResponseBody->Close();
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ EnsureSynthesizedResponse();
+
+ nsCOMPtr<nsIURI> originalURI;
+ mChannel->GetURI(getter_AddRefs(originalURI));
+
+ nsCOMPtr<nsIURI> responseURI;
+ if (!aFinalURLSpec.IsEmpty()) {
+ nsresult rv = NS_NewURI(getter_AddRefs(responseURI), aFinalURLSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (mSecureUpgrade) {
+ nsresult rv = NS_GetSecureUpgradedURI(originalURI,
+ getter_AddRefs(responseURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ responseURI = originalURI;
+ }
+
+ bool equal = false;
+ originalURI->Equals(responseURI, &equal);
+ if (!equal) {
+ mChannel->ForceIntercepted(mSynthesizedInput);
+ mChannel->BeginNonIPCRedirect(responseURI, *mSynthesizedResponseHead.ptr());
+ } else {
+ mChannel->OverrideWithSynthesizedResponse(mSynthesizedResponseHead.ref(),
+ mSynthesizedInput,
+ mStreamListener);
+ }
+
+ mResponseBody = nullptr;
+ mStreamListener = nullptr;
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::Cancel(nsresult aStatus)
+{
+ MOZ_ASSERT(NS_FAILED(aStatus));
+
+ if (mClosed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mReportCollector->FlushConsoleReports(mChannel);
+
+ // we need to use AsyncAbort instead of Cancel since there's no active pump
+ // to cancel which will provide OnStart/OnStopRequest to the channel.
+ nsresult rv = mChannel->AsyncAbort(aStatus);
+ NS_ENSURE_SUCCESS(rv, rv);
+ mStreamListener = nullptr;
+ mClosed = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::SetChannelInfo(dom::ChannelInfo* aChannelInfo)
+{
+ if (mClosed) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return aChannelInfo->ResurrectInfoOnChannel(mChannel);
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::GetInternalContentPolicyType(nsContentPolicyType* aPolicyType)
+{
+ NS_ENSURE_ARG(aPolicyType);
+
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ nsresult rv = mChannel->GetLoadInfo(getter_AddRefs(loadInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *aPolicyType = loadInfo->InternalContentPolicyType();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedChannelContent::GetSecureUpgradedChannelURI(nsIURI** aURI)
+{
+ nsCOMPtr<nsIURI> uri;
+ if (mSecureUpgrade) {
+ uri = SecureUpgradeChannelURI(mChannel);
+ } else {
+ nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ if (uri) {
+ uri.forget(aURI);
+ return NS_OK;
+ }
+ return NS_ERROR_FAILURE;
+}
+
+} // namespace net
+} // namespace mozilla