Blob Blame History Raw
/* -*- 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 "nsICancelable.h"
#include "nsInputStreamPump.h"
#include "nsIPipe.h"
#include "nsIStreamListener.h"
#include "nsITimedChannel.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"
#include "nsThreadUtils.h"

namespace mozilla {
namespace net {

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),
      mSynthesizedOrReset(Invalid) {}

InterceptedChannelBase::~InterceptedChannelBase() {}

void InterceptedChannelBase::EnsureSynthesizedResponse() {
  if (mSynthesizedResponseHead.isNothing()) {
    mSynthesizedResponseHead.emplace(new nsHttpResponseHead());
  }
}

void InterceptedChannelBase::DoNotifyController() {
  nsresult rv = NS_OK;

  if (NS_WARN_IF(!mController)) {
    rv = ResetInterception();
    if (NS_FAILED(rv)) {
      NS_WARNING("Failed to resume intercepted network request");
      CancelInterception(rv);
    }
    return;
  }

  rv = mController->ChannelIntercepted(this);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    rv = ResetInterception();
    if (NS_FAILED(rv)) {
      NS_WARNING("Failed to resume intercepted network request");
      CancelInterception(rv);
    }
  }
  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;
}

NS_IMETHODIMP
InterceptedChannelBase::SaveTimeStamps() {
  MOZ_ASSERT(NS_IsMainThread());

  nsCOMPtr<nsIChannel> underlyingChannel;
  nsresult rv = GetChannel(getter_AddRefs(underlyingChannel));
  MOZ_ASSERT(NS_SUCCEEDED(rv));

  nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(underlyingChannel);
  MOZ_ASSERT(timedChannel);

  rv = timedChannel->SetLaunchServiceWorkerStart(mLaunchServiceWorkerStart);
  MOZ_ASSERT(NS_SUCCEEDED(rv));

  rv = timedChannel->SetLaunchServiceWorkerEnd(mLaunchServiceWorkerEnd);
  MOZ_ASSERT(NS_SUCCEEDED(rv));

  rv = timedChannel->SetDispatchFetchEventStart(mDispatchFetchEventStart);
  MOZ_ASSERT(NS_SUCCEEDED(rv));

  rv = timedChannel->SetDispatchFetchEventEnd(mDispatchFetchEventEnd);
  MOZ_ASSERT(NS_SUCCEEDED(rv));

  rv = timedChannel->SetHandleFetchEventStart(mHandleFetchEventStart);
  MOZ_ASSERT(NS_SUCCEEDED(rv));

  rv = timedChannel->SetHandleFetchEventEnd(mHandleFetchEventEnd);
  MOZ_ASSERT(NS_SUCCEEDED(rv));

  nsCOMPtr<nsIChannel> channel;
  GetChannel(getter_AddRefs(channel));
  if (NS_WARN_IF(!channel)) {
    return NS_ERROR_FAILURE;
  }

  bool isNonSubresourceRequest =
      nsContentUtils::IsNonSubresourceRequest(channel);
  nsCString navigationOrSubresource = isNonSubresourceRequest
                                          ? NS_LITERAL_CSTRING("navigation")
                                          : NS_LITERAL_CSTRING("subresource");

  nsAutoCString subresourceKey(EmptyCString());
  GetSubresourceTimeStampKey(channel, subresourceKey);

  // We may have null timestamps if the fetch dispatch runnable was cancelled
  // and we defaulted to resuming the request.
  if (!mFinishResponseStart.IsNull() && !mFinishResponseEnd.IsNull()) {
    MOZ_ASSERT(mSynthesizedOrReset != Invalid);

    Telemetry::HistogramID id =
        (mSynthesizedOrReset == Synthesized)
            ? Telemetry::
                  SERVICE_WORKER_FETCH_EVENT_FINISH_SYNTHESIZED_RESPONSE_MS
            : Telemetry::SERVICE_WORKER_FETCH_EVENT_CHANNEL_RESET_MS;
    Telemetry::Accumulate(
        id, navigationOrSubresource,
        static_cast<uint32_t>(
            (mFinishResponseEnd - mFinishResponseStart).ToMilliseconds()));
    if (!isNonSubresourceRequest && !subresourceKey.IsEmpty()) {
      Telemetry::Accumulate(
          id, subresourceKey,
          static_cast<uint32_t>(
              (mFinishResponseEnd - mFinishResponseStart).ToMilliseconds()));
    }
  }

  Telemetry::Accumulate(
      Telemetry::SERVICE_WORKER_FETCH_EVENT_DISPATCH_MS,
      navigationOrSubresource,
      static_cast<uint32_t>((mHandleFetchEventStart - mDispatchFetchEventStart)
                                .ToMilliseconds()));

  if (!isNonSubresourceRequest && !subresourceKey.IsEmpty()) {
    Telemetry::Accumulate(Telemetry::SERVICE_WORKER_FETCH_EVENT_DISPATCH_MS,
                          subresourceKey,
                          static_cast<uint32_t>((mHandleFetchEventStart -
                                                 mDispatchFetchEventStart)
                                                    .ToMilliseconds()));
  }

  if (!mFinishResponseEnd.IsNull()) {
    Telemetry::Accumulate(
        Telemetry::SERVICE_WORKER_FETCH_INTERCEPTION_DURATION_MS,
        navigationOrSubresource,
        static_cast<uint32_t>(
            (mFinishResponseEnd - mDispatchFetchEventStart).ToMilliseconds()));
    if (!isNonSubresourceRequest && !subresourceKey.IsEmpty()) {
      Telemetry::Accumulate(
          Telemetry::SERVICE_WORKER_FETCH_INTERCEPTION_DURATION_MS,
          subresourceKey,
          static_cast<uint32_t>((mFinishResponseEnd - mDispatchFetchEventStart)
                                    .ToMilliseconds()));
    }
  }

  return rv;
}

/* 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();
}

InterceptedChannelContent::InterceptedChannelContent(
    HttpChannelChild* aChannel, nsINetworkInterceptController* aController,
    InterceptStreamListener* aListener, bool aSecureUpgrade)
    : InterceptedChannelBase(aController),
      mChannel(aChannel),
      mStreamListener(aListener),
      mSecureUpgrade(aSecureUpgrade) {}

void InterceptedChannelContent::NotifyController() { 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);

  mChannel->ResetInterception();

  mClosed = true;

  return NS_OK;
}

NS_IMETHODIMP
InterceptedChannelContent::SynthesizeStatus(uint16_t aStatus,
                                            const nsACString& aReason) {
  if (mClosed) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  return DoSynthesizeStatus(aStatus, aReason);
}

NS_IMETHODIMP
InterceptedChannelContent::SynthesizeHeader(const nsACString& aName,
                                            const nsACString& aValue) {
  if (mClosed) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  return DoSynthesizeHeader(aName, aValue);
}

NS_IMETHODIMP
InterceptedChannelContent::StartSynthesizedResponse(
    nsIInputStream* aBody, nsIInterceptedBodyCallback* aBodyCallback,
    nsICacheInfoChannel* aCacheInfoChannel, const nsACString& aFinalURLSpec,
    bool aResponseRedirected) {
  if (NS_WARN_IF(mClosed)) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  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(aBody, aBodyCallback, aCacheInfoChannel);
    mChannel->BeginNonIPCRedirect(responseURI, *mSynthesizedResponseHead.ptr(),
                                  aResponseRedirected);
  } else {
    mChannel->OverrideWithSynthesizedResponse(
        mSynthesizedResponseHead.ref(), aBody, aBodyCallback, mStreamListener,
        aCacheInfoChannel);
  }

  return NS_OK;
}

NS_IMETHODIMP
InterceptedChannelContent::FinishSynthesizedResponse() {
  if (NS_WARN_IF(mClosed)) {
    return NS_ERROR_NOT_AVAILABLE;
  }

  mReportCollector->FlushConsoleReports(mChannel);

  mStreamListener = nullptr;
  mClosed = true;

  return NS_OK;
}

NS_IMETHODIMP
InterceptedChannelContent::CancelInterception(nsresult aStatus) {
  MOZ_ASSERT(NS_FAILED(aStatus));

  if (mClosed) {
    return NS_ERROR_FAILURE;
  }
  mClosed = true;

  mReportCollector->FlushConsoleReports(mChannel);

  Unused << mChannel->Cancel(aStatus);
  mStreamListener = nullptr;

  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);

  if (loadInfo) {
    *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