summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/InterceptedHttpChannel.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--netwerk/protocol/http/InterceptedHttpChannel.cpp1631
1 files changed, 1631 insertions, 0 deletions
diff --git a/netwerk/protocol/http/InterceptedHttpChannel.cpp b/netwerk/protocol/http/InterceptedHttpChannel.cpp
new file mode 100644
index 0000000000..c493259905
--- /dev/null
+++ b/netwerk/protocol/http/InterceptedHttpChannel.cpp
@@ -0,0 +1,1631 @@
+/* -*- 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/. */
+
+#include "InterceptedHttpChannel.h"
+#include "NetworkMarker.h"
+#include "nsContentSecurityManager.h"
+#include "nsEscape.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/dom/ChannelInfo.h"
+#include "mozilla/dom/PerformanceStorage.h"
+#include "nsHttpChannel.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsIRedirectResultListener.h"
+#include "nsStringStream.h"
+#include "nsStreamUtils.h"
+#include "nsQueryObject.h"
+#include "mozilla/Logging.h"
+
+namespace mozilla::net {
+
+mozilla::LazyLogModule gInterceptedLog("Intercepted");
+
+#define INTERCEPTED_LOG(args) MOZ_LOG(gInterceptedLog, LogLevel::Debug, args)
+
+NS_IMPL_ISUPPORTS_INHERITED(InterceptedHttpChannel, HttpBaseChannel,
+ nsIInterceptedChannel, nsICacheInfoChannel,
+ nsIAsyncVerifyRedirectCallback, nsIRequestObserver,
+ nsIStreamListener, nsIThreadRetargetableRequest,
+ nsIThreadRetargetableStreamListener,
+ nsIClassOfService)
+
+InterceptedHttpChannel::InterceptedHttpChannel(
+ PRTime aCreationTime, const TimeStamp& aCreationTimestamp,
+ const TimeStamp& aAsyncOpenTimestamp)
+ : HttpAsyncAborter<InterceptedHttpChannel>(this),
+ mProgress(0),
+ mProgressReported(0),
+ mSynthesizedStreamLength(-1),
+ mResumeStartPos(0),
+ mCallingStatusAndProgress(false),
+ mTimeStamps() {
+ // Pre-set the creation and AsyncOpen times based on the original channel
+ // we are intercepting. We don't want our extra internal redirect to mask
+ // any time spent processing the channel.
+ INTERCEPTED_LOG(("Creating InterceptedHttpChannel [%p]", this));
+ mChannelCreationTime = aCreationTime;
+ mChannelCreationTimestamp = aCreationTimestamp;
+ mInterceptedChannelCreationTimestamp = TimeStamp::Now();
+ mAsyncOpenTime = aAsyncOpenTimestamp;
+}
+
+void InterceptedHttpChannel::ReleaseListeners() {
+ if (mLoadGroup) {
+ mLoadGroup->RemoveRequest(this, nullptr, mStatus);
+ }
+ HttpBaseChannel::ReleaseListeners();
+ mSynthesizedResponseHead.reset();
+ mRedirectChannel = nullptr;
+ mBodyReader = nullptr;
+ mReleaseHandle = nullptr;
+ mProgressSink = nullptr;
+ mBodyCallback = nullptr;
+ mPump = nullptr;
+
+ MOZ_DIAGNOSTIC_ASSERT(!LoadIsPending());
+}
+
+nsresult InterceptedHttpChannel::SetupReplacementChannel(
+ nsIURI* aURI, nsIChannel* aChannel, bool aPreserveMethod,
+ uint32_t aRedirectFlags) {
+ INTERCEPTED_LOG(
+ ("InterceptedHttpChannel::SetupReplacementChannel [%p] flag: %u", this,
+ aRedirectFlags));
+ nsresult rv = HttpBaseChannel::SetupReplacementChannel(
+ aURI, aChannel, aPreserveMethod, aRedirectFlags);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = CheckRedirectLimit(aRedirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // While we can't resume an synthetic response, we can still propagate
+ // the resume params across redirects for other channels to handle.
+ if (mResumeStartPos > 0) {
+ nsCOMPtr<nsIResumableChannel> resumable = do_QueryInterface(aChannel);
+ if (!resumable) {
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+
+ resumable->ResumeAt(mResumeStartPos, mResumeEntityId);
+ }
+
+ return NS_OK;
+}
+
+void InterceptedHttpChannel::AsyncOpenInternal() {
+ // We save this timestamp from outside of the if block in case we enable the
+ // profiler after AsyncOpen().
+ INTERCEPTED_LOG(("InterceptedHttpChannel::AsyncOpenInternal [%p]", this));
+ mLastStatusReported = TimeStamp::Now();
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START,
+ mChannelCreationTimestamp, mLastStatusReported, 0, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0);
+ }
+
+ // If an error occurs in this file we must ensure mListener callbacks are
+ // invoked in some way. We either Cancel() or ResetInterception below
+ // depending on which path we take.
+ nsresult rv = NS_OK;
+
+ // Start the interception, record the start time.
+ mTimeStamps.Init(this);
+ mTimeStamps.RecordTime();
+
+ // We should have pre-set the AsyncOpen time based on the original channel if
+ // timings are enabled.
+ if (LoadTimingEnabled()) {
+ MOZ_DIAGNOSTIC_ASSERT(!mAsyncOpenTime.IsNull());
+ }
+
+ StoreIsPending(true);
+ StoreResponseCouldBeSynthesized(true);
+
+ if (mLoadGroup) {
+ mLoadGroup->AddRequest(this, nullptr);
+ }
+
+ // If we already have a synthesized body then we are pre-synthesized.
+ // This can happen for two reasons:
+ // 1. We have a pre-synthesized redirect in e10s mode. In this case
+ // we should follow the redirect.
+ // 2. We are handling a "fake" redirect for an opaque response. Here
+ // we should just process the synthetic body.
+ if (mBodyReader) {
+ // If we fail in this path, then cancel the channel. We don't want
+ // to ResetInterception() after a synthetic result has already been
+ // produced by the ServiceWorker.
+ auto autoCancel = MakeScopeExit([&] {
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+ });
+
+ // The fetch event will not be dispatched, record current time for
+ // FetchHandlerStart and FetchHandlerFinish.
+ SetFetchHandlerStart(TimeStamp::Now());
+ SetFetchHandlerFinish(TimeStamp::Now());
+
+ if (ShouldRedirect()) {
+ rv = FollowSyntheticRedirect();
+ return;
+ }
+
+ rv = StartPump();
+ return;
+ }
+
+ // If we fail the initial interception, then attempt to ResetInterception
+ // to fall back to network. We only cancel if the reset fails.
+ auto autoReset = MakeScopeExit([&] {
+ if (NS_FAILED(rv)) {
+ rv = ResetInterception(false);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Cancel(rv);
+ }
+ }
+ });
+
+ // Otherwise we need to trigger a FetchEvent in a ServiceWorker.
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ GetCallback(controller);
+
+ if (NS_WARN_IF(!controller)) {
+ rv = NS_ERROR_DOM_INVALID_STATE_ERR;
+ return;
+ }
+
+ rv = controller->ChannelIntercepted(this);
+ NS_ENSURE_SUCCESS_VOID(rv);
+}
+
+bool InterceptedHttpChannel::ShouldRedirect() const {
+ // Determine if the synthetic response requires us to perform a real redirect.
+ return nsHttpChannel::WillRedirect(*mResponseHead) &&
+ !mLoadInfo->GetDontFollowRedirects();
+}
+
+nsresult InterceptedHttpChannel::FollowSyntheticRedirect() {
+ // Perform a real redirect based on the synthetic response.
+
+ nsCOMPtr<nsIIOService> ioService;
+ nsresult rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString location;
+ rv = mResponseHead->GetHeader(nsHttp::Location, location);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ // make sure non-ASCII characters in the location header are escaped.
+ nsAutoCString locationBuf;
+ if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII | esc_Spaces,
+ locationBuf)) {
+ location = locationBuf;
+ }
+
+ nsCOMPtr<nsIURI> redirectURI;
+ rv = ioService->NewURI(nsDependentCString(location.get()), nullptr, mURI,
+ getter_AddRefs(redirectURI));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_CORRUPTED_CONTENT);
+
+ uint32_t redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
+ if (nsHttp::IsPermanentRedirect(mResponseHead->Status())) {
+ redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
+ }
+
+ PropagateReferenceIfNeeded(mURI, redirectURI);
+
+ bool rewriteToGET = ShouldRewriteRedirectToGET(mResponseHead->Status(),
+ mRequestHead.ParsedMethod());
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(redirectURI, redirectFlags);
+ rv = NS_NewChannelInternal(getter_AddRefs(newChannel), redirectURI,
+ redirectLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ mLoadFlags, ioService);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = SetupReplacementChannel(redirectURI, newChannel, !rewriteToGET,
+ redirectFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRedirectChannel = std::move(newChannel);
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, mRedirectChannel,
+ redirectFlags);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ OnRedirectVerifyCallback(rv);
+ } else {
+ // Redirect success, record the finish time and the final status.
+ mTimeStamps.RecordTime(InterceptionTimeStamps::Redirected);
+ }
+
+ return rv;
+}
+
+nsresult InterceptedHttpChannel::RedirectForResponseURL(
+ nsIURI* aResponseURI, bool aResponseRedirected) {
+ // Perform a service worker redirect to another InterceptedHttpChannel using
+ // the given response URL. It allows content to see the final URL where
+ // appropriate and also helps us enforce cross-origin restrictions. The
+ // resulting channel will then process the synthetic response as normal. This
+ // extra redirect is performed so that listeners treat the result as unsafe
+ // cross-origin data.
+
+ nsresult rv = NS_OK;
+
+ // We want to pass ownership of the body callback to the new synthesized
+ // channel. We need to hold a reference to the callbacks on the stack
+ // as well, though, so we can call them if a failure occurs.
+ nsCOMPtr<nsIInterceptedBodyCallback> bodyCallback = std::move(mBodyCallback);
+
+ RefPtr<InterceptedHttpChannel> newChannel = CreateForSynthesis(
+ mResponseHead.get(), mBodyReader, bodyCallback, mChannelCreationTime,
+ mChannelCreationTimestamp, mAsyncOpenTime);
+
+ // If the response has been redirected, propagate all the URLs to content.
+ // Thus, the exact value of the redirect flag does not matter as long as it's
+ // not REDIRECT_INTERNAL.
+ uint32_t flags = aResponseRedirected ? nsIChannelEventSink::REDIRECT_TEMPORARY
+ : nsIChannelEventSink::REDIRECT_INTERNAL;
+
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(aResponseURI, flags);
+
+ ExtContentPolicyType contentPolicyType =
+ redirectLoadInfo->GetExternalContentPolicyType();
+
+ rv = newChannel->Init(aResponseURI, mCaps,
+ static_cast<nsProxyInfo*>(mProxyInfo.get()),
+ mProxyResolveFlags, mProxyURI, mChannelId,
+ contentPolicyType, redirectLoadInfo);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Normally we don't propagate the LoadInfo's service worker tainting
+ // synthesis flag on redirect. A real redirect normally will want to allow
+ // normal tainting to proceed from its starting taint. For this particular
+ // redirect, though, we are performing a redirect to communicate the URL of
+ // the service worker synthetic response itself. This redirect still
+ // represents the synthetic response, so we must preserve the flag.
+ if (redirectLoadInfo && mLoadInfo &&
+ mLoadInfo->GetServiceWorkerTaintingSynthesized()) {
+ redirectLoadInfo->SynthesizeServiceWorkerTainting(mLoadInfo->GetTainting());
+ }
+
+ rv = SetupReplacementChannel(aResponseURI, newChannel, true, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mRedirectChannel = newChannel;
+
+ MOZ_ASSERT(mBodyReader);
+ MOZ_ASSERT(!LoadApplyConversion());
+ newChannel->SetApplyConversion(false);
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, mRedirectChannel, flags);
+
+ if (NS_FAILED(rv)) {
+ // Make sure to call the body callback since we took ownership
+ // above. Neither the new channel or our standard
+ // OnRedirectVerifyCallback() code will invoke the callback. Do it here.
+ bodyCallback->BodyComplete(rv);
+
+ OnRedirectVerifyCallback(rv);
+ }
+
+ return rv;
+}
+
+nsresult InterceptedHttpChannel::StartPump() {
+ MOZ_DIAGNOSTIC_ASSERT(!mPump);
+ MOZ_DIAGNOSTIC_ASSERT(mBodyReader);
+
+ // We don't support resuming an intercepted channel. We can't guarantee the
+ // ServiceWorker will always return the same data and we can't rely on the
+ // http cache code to detect changes. For now, just force the channel to
+ // NS_ERROR_NOT_RESUMABLE which should cause the front-end to recreate the
+ // channel without calling ResumeAt().
+ //
+ // It would also be possible to convert this information to a range request,
+ // but its unclear if we should do that for ServiceWorker FetchEvents. See:
+ //
+ // https://github.com/w3c/ServiceWorker/issues/1201
+ if (mResumeStartPos > 0) {
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+
+ // For progress we trust the content-length for the "maximum" size.
+ // We can't determine the full size from the stream itself since
+ // we may only receive the data incrementally. We can't trust
+ // Available() here.
+ // TODO: We could implement an nsIFixedLengthInputStream interface and
+ // QI to it here. This would let us determine the total length
+ // for streams that support it. See bug 1388774.
+ Unused << GetContentLength(&mSynthesizedStreamLength);
+
+ nsresult rv =
+ nsInputStreamPump::Create(getter_AddRefs(mPump), mBodyReader, 0, 0, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mPump->AsyncRead(this);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t suspendCount = mSuspendCount;
+ while (suspendCount--) {
+ mPump->Suspend();
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!mCanceled);
+
+ return rv;
+}
+
+nsresult InterceptedHttpChannel::OpenRedirectChannel() {
+ INTERCEPTED_LOG(
+ ("InterceptedHttpChannel::OpenRedirectChannel [%p], mRedirectChannel: %p",
+ this, mRedirectChannel.get()));
+ nsresult rv = NS_OK;
+
+ if (NS_FAILED(mStatus)) {
+ return mStatus;
+ }
+
+ if (!mRedirectChannel) {
+ return NS_ERROR_DOM_ABORT_ERR;
+ }
+
+ // Make sure to do this after we received redirect veto answer,
+ // i.e. after all sinks had been notified
+ mRedirectChannel->SetOriginalURI(mOriginalURI);
+
+ // open new channel
+ rv = mRedirectChannel->AsyncOpen(mListener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mStatus = NS_BINDING_REDIRECTED;
+
+ return rv;
+}
+
+void InterceptedHttpChannel::MaybeCallStatusAndProgress() {
+ // OnStatus() and OnProgress() must only be called on the main thread. If
+ // we are on a separate thread, then we maybe need to schedule a runnable
+ // to call them asynchronousnly.
+ if (!NS_IsMainThread()) {
+ // Check to see if we are already trying to call OnStatus/OnProgress
+ // asynchronously. If we are, then don't queue up another runnable.
+ // We don't want to flood the main thread.
+ if (mCallingStatusAndProgress) {
+ return;
+ }
+ mCallingStatusAndProgress = true;
+
+ nsCOMPtr<nsIRunnable> r = NewRunnableMethod(
+ "InterceptedHttpChannel::MaybeCallStatusAndProgress", this,
+ &InterceptedHttpChannel::MaybeCallStatusAndProgress);
+ MOZ_ALWAYS_SUCCEEDS(
+ SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()));
+
+ return;
+ }
+
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We are about to capture out progress position. Clear the flag we use
+ // to de-duplicate progress report runnables. We want any further progress
+ // updates to trigger another runnable. We do this before capture the
+ // progress value since we're using atomics and not a mutex lock.
+ mCallingStatusAndProgress = false;
+
+ // Capture the current status from our atomic count.
+ int64_t progress = mProgress;
+
+ MOZ_DIAGNOSTIC_ASSERT(progress >= mProgressReported);
+
+ // Do nothing if we've already made the calls for this amount of progress
+ // or if the channel is not configured for these calls. Note, the check
+ // for mProgressSink here means we will not fire any spurious late calls
+ // after ReleaseListeners() is executed.
+ if (progress <= mProgressReported || mCanceled || !mProgressSink ||
+ (mLoadFlags & HttpBaseChannel::LOAD_BACKGROUND)) {
+ return;
+ }
+
+ // Capture the host name on the first set of calls to avoid doing this
+ // string processing repeatedly.
+ if (mProgressReported == 0) {
+ nsAutoCString host;
+ MOZ_ALWAYS_SUCCEEDS(mURI->GetHost(host));
+ CopyUTF8toUTF16(host, mStatusHost);
+ }
+
+ mProgressSink->OnStatus(this, NS_NET_STATUS_READING, mStatusHost.get());
+
+ mProgressSink->OnProgress(this, progress, mSynthesizedStreamLength);
+
+ mProgressReported = progress;
+}
+
+void InterceptedHttpChannel::MaybeCallBodyCallback() {
+ nsCOMPtr<nsIInterceptedBodyCallback> callback = std::move(mBodyCallback);
+ if (callback) {
+ callback->BodyComplete(mStatus);
+ }
+}
+
+// static
+already_AddRefed<InterceptedHttpChannel>
+InterceptedHttpChannel::CreateForInterception(
+ PRTime aCreationTime, const TimeStamp& aCreationTimestamp,
+ const TimeStamp& aAsyncOpenTimestamp) {
+ // Create an InterceptedHttpChannel that will trigger a FetchEvent
+ // in a ServiceWorker when opened.
+ RefPtr<InterceptedHttpChannel> ref = new InterceptedHttpChannel(
+ aCreationTime, aCreationTimestamp, aAsyncOpenTimestamp);
+
+ return ref.forget();
+}
+
+// static
+already_AddRefed<InterceptedHttpChannel>
+InterceptedHttpChannel::CreateForSynthesis(
+ const nsHttpResponseHead* aHead, nsIInputStream* aBody,
+ nsIInterceptedBodyCallback* aBodyCallback, PRTime aCreationTime,
+ const TimeStamp& aCreationTimestamp, const TimeStamp& aAsyncOpenTimestamp) {
+ MOZ_DIAGNOSTIC_ASSERT(aHead);
+ MOZ_DIAGNOSTIC_ASSERT(aBody);
+
+ // Create an InterceptedHttpChannel that already has a synthesized response.
+ // The synthetic response will be processed when opened. A FetchEvent
+ // will not be triggered.
+ RefPtr<InterceptedHttpChannel> ref = new InterceptedHttpChannel(
+ aCreationTime, aCreationTimestamp, aAsyncOpenTimestamp);
+
+ ref->mResponseHead = MakeUnique<nsHttpResponseHead>(*aHead);
+ ref->mBodyReader = aBody;
+ ref->mBodyCallback = aBodyCallback;
+
+ return ref.forget();
+}
+
+NS_IMETHODIMP InterceptedHttpChannel::SetCanceledReason(
+ const nsACString& aReason) {
+ return SetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP InterceptedHttpChannel::GetCanceledReason(nsACString& aReason) {
+ return GetCanceledReasonImpl(aReason);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::CancelWithReason(nsresult aStatus,
+ const nsACString& aReason) {
+ return CancelWithReasonImpl(aStatus, aReason);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::Cancel(nsresult aStatus) {
+ INTERCEPTED_LOG(("InterceptedHttpChannel::Cancel [%p]", this));
+ // Note: This class has been designed to send all error results through
+ // Cancel(). Don't add calls directly to AsyncAbort() or
+ // DoNotifyListener(). Instead call Cancel().
+
+ if (mCanceled) {
+ return NS_OK;
+ }
+
+ // The interception is canceled, record the finish time stamp and the final
+ // status
+ mTimeStamps.RecordTime(InterceptionTimeStamps::Canceled);
+
+ mCanceled = true;
+
+ if (mLastStatusReported && profiler_thread_is_being_profiled_for_markers()) {
+ // These do allocations/frees/etc; avoid if not active
+ // mLastStatusReported can be null if Cancel is called before we added the
+ // start marker.
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+
+ uint64_t size = 0;
+ GetEncodedBodySize(&size);
+
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_CANCEL,
+ mLastStatusReported, TimeStamp::Now(), size, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource));
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(aStatus));
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+
+ if (mPump) {
+ return mPump->Cancel(mStatus);
+ }
+
+ return AsyncAbort(mStatus);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::Suspend(void) {
+ ++mSuspendCount;
+ if (mPump) {
+ return mPump->Suspend();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::Resume(void) {
+ --mSuspendCount;
+ if (mPump) {
+ return mPump->Resume();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetSecurityInfo(
+ nsITransportSecurityInfo** aSecurityInfo) {
+ nsCOMPtr<nsITransportSecurityInfo> ref(mSecurityInfo);
+ ref.forget(aSecurityInfo);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::AsyncOpen(nsIStreamListener* aListener) {
+ INTERCEPTED_LOG(("InterceptedHttpChannel::AsyncOpen [%p], listener: %p", this,
+ aListener));
+ nsCOMPtr<nsIStreamListener> listener(aListener);
+
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ Cancel(rv);
+ return rv;
+ }
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ // After this point we should try to return NS_OK and notify the listener
+ // of the result.
+ mListener = aListener;
+
+ AsyncOpenInternal();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::LogBlockedCORSRequest(const nsAString& aMessage,
+ const nsACString& aCategory,
+ bool aIsWarning) {
+ // Synthetic responses should not trigger CORS blocking.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::LogMimeTypeMismatch(const nsACString& aMessageName,
+ bool aWarning,
+ const nsAString& aURL,
+ const nsAString& aContentType) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetIsAuthChannel(bool* aIsAuthChannel) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetPriority(int32_t aPriority) {
+ mPriority = clamped<int32_t>(aPriority, INT16_MIN, INT16_MAX);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetClassFlags(uint32_t aClassFlags) {
+ mClassOfService.SetFlags(aClassFlags);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::ClearClassFlags(uint32_t aClassFlags) {
+ mClassOfService.SetFlags(~aClassFlags & mClassOfService.Flags());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::AddClassFlags(uint32_t aClassFlags) {
+ mClassOfService.SetFlags(aClassFlags | mClassOfService.Flags());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetClassOfService(ClassOfService cos) {
+ mClassOfService = cos;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetIncremental(bool incremental) {
+ mClassOfService.SetIncremental(incremental);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::ResumeAt(uint64_t aStartPos,
+ const nsACString& aEntityId) {
+ // We don't support resuming synthesized responses, but we do track this
+ // information so it can be passed on to the resulting nsHttpChannel if
+ // ResetInterception is called.
+ mResumeStartPos = aStartPos;
+ mResumeEntityId = aEntityId;
+ return NS_OK;
+}
+
+void InterceptedHttpChannel::DoNotifyListenerCleanup() {
+ // Prefer to cleanup in ReleaseListeners() as it seems to be called
+ // more consistently in necko.
+}
+
+void InterceptedHttpChannel::DoAsyncAbort(nsresult aStatus) {
+ Unused << AsyncAbort(aStatus);
+}
+
+namespace {
+
+class ResetInterceptionHeaderVisitor final : public nsIHttpHeaderVisitor {
+ nsCOMPtr<nsIHttpChannel> mTarget;
+
+ ~ResetInterceptionHeaderVisitor() = default;
+
+ NS_IMETHOD
+ VisitHeader(const nsACString& aHeader, const nsACString& aValue) override {
+ // We skip Cookie header here, since it will be added during
+ // nsHttpChannel::AsyncOpen.
+ if (aHeader.Equals(nsHttp::Cookie)) {
+ return NS_OK;
+ }
+ if (aValue.IsEmpty()) {
+ return mTarget->SetEmptyRequestHeader(aHeader);
+ }
+ return mTarget->SetRequestHeader(aHeader, aValue, false /* merge */);
+ }
+
+ public:
+ explicit ResetInterceptionHeaderVisitor(nsIHttpChannel* aTarget)
+ : mTarget(aTarget) {
+ MOZ_DIAGNOSTIC_ASSERT(mTarget);
+ }
+
+ NS_DECL_ISUPPORTS
+};
+
+NS_IMPL_ISUPPORTS(ResetInterceptionHeaderVisitor, nsIHttpHeaderVisitor)
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+InterceptedHttpChannel::ResetInterception(bool aBypass) {
+ INTERCEPTED_LOG(("InterceptedHttpChannel::ResetInterception [%p] bypass: %s",
+ this, aBypass ? "true" : "false"));
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ mInterceptionReset = true;
+
+ uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL;
+
+ nsCOMPtr<nsIChannel> newChannel;
+ nsCOMPtr<nsILoadInfo> redirectLoadInfo =
+ CloneLoadInfoForRedirect(mURI, flags);
+
+ if (aBypass) {
+ redirectLoadInfo->ClearController();
+ // TODO: Audit whether we should also be calling
+ // ServiceWorkerManager::StopControllingClient for maximum correctness.
+ }
+
+ nsresult rv =
+ NS_NewChannelInternal(getter_AddRefs(newChannel), mURI, redirectLoadInfo,
+ nullptr, // PerformanceStorage
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ mLoadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+
+ uint64_t size = 0;
+ GetEncodedBodySize(&size);
+
+ nsAutoCString contentType;
+ if (mResponseHead) {
+ mResponseHead->ContentType(contentType);
+ }
+
+ RefPtr<HttpBaseChannel> newBaseChannel = do_QueryObject(newChannel);
+ MOZ_ASSERT(newBaseChannel,
+ "The redirect channel should be a base channel.");
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId,
+ NetworkLoadType::LOAD_REDIRECT, mLastStatusReported, TimeStamp::Now(),
+ size, kCacheUnknown, mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource),
+ Some(nsDependentCString(contentType.get())), mURI, flags,
+ newBaseChannel->ChannelId());
+ }
+
+ rv = SetupReplacementChannel(mURI, newChannel, true, flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Restore the non-default headers for fallback channel.
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(newChannel));
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+ new ResetInterceptionHeaderVisitor(httpChannel);
+ rv = VisitNonDefaultRequestHeaders(visitor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsITimedChannel> newTimedChannel = do_QueryInterface(newChannel);
+ if (newTimedChannel) {
+ if (!mAsyncOpenTime.IsNull()) {
+ newTimedChannel->SetAsyncOpen(mAsyncOpenTime);
+ }
+ if (!mChannelCreationTimestamp.IsNull()) {
+ newTimedChannel->SetChannelCreation(mChannelCreationTimestamp);
+ }
+ }
+
+ if (mRedirectMode != nsIHttpChannelInternal::REDIRECT_MODE_MANUAL) {
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
+ rv = newChannel->GetLoadFlags(&loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
+ rv = newChannel->SetLoadFlags(loadFlags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mRedirectChannel = std::move(newChannel);
+
+ rv = gHttpHandler->AsyncOnChannelRedirect(this, mRedirectChannel, flags);
+
+ if (NS_FAILED(rv)) {
+ OnRedirectVerifyCallback(rv);
+ } else {
+ // ResetInterception success, record the finish time stamps and the final
+ // status.
+ mTimeStamps.RecordTime(InterceptionTimeStamps::Reset);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SynthesizeStatus(uint16_t aStatus,
+ const nsACString& aReason) {
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (!mSynthesizedResponseHead) {
+ mSynthesizedResponseHead.reset(new nsHttpResponseHead());
+ }
+
+ nsAutoCString statusLine;
+ statusLine.AppendLiteral("HTTP/1.1 ");
+ statusLine.AppendInt(aStatus);
+ statusLine.AppendLiteral(" ");
+ statusLine.Append(aReason);
+
+ mSynthesizedResponseHead->ParseStatusLine(statusLine);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SynthesizeHeader(const nsACString& aName,
+ const nsACString& aValue) {
+ if (mCanceled) {
+ return mStatus;
+ }
+
+ if (!mSynthesizedResponseHead) {
+ mSynthesizedResponseHead.reset(new nsHttpResponseHead());
+ }
+
+ nsAutoCString header = aName + ": "_ns + aValue;
+ // Overwrite any existing header.
+ nsresult rv = mSynthesizedResponseHead->ParseHeaderLine(header);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::StartSynthesizedResponse(
+ nsIInputStream* aBody, nsIInterceptedBodyCallback* aBodyCallback,
+ nsICacheInfoChannel* aSynthesizedCacheInfo, const nsACString& aFinalURLSpec,
+ bool aResponseRedirected) {
+ nsresult rv = NS_OK;
+
+ auto autoCleanup = MakeScopeExit([&] {
+ // Auto-cancel on failure. Do this first to get mStatus set, if necessary.
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+
+ // If we early exit before taking ownership of the body, then automatically
+ // invoke the callback. This could be due to an error or because we're not
+ // going to consume it due to a redirect, etc.
+ if (aBodyCallback) {
+ aBodyCallback->BodyComplete(mStatus);
+ }
+ });
+
+ if (NS_FAILED(mStatus)) {
+ // Return NS_OK. The channel should fire callbacks with an error code
+ // if it was cancelled before this point.
+ return NS_OK;
+ }
+
+ // Take ownership of the body callbacks If a failure occurs we will
+ // automatically Cancel() the channel. This will then invoke OnStopRequest()
+ // which will invoke the correct callback. In the case of an opaque response
+ // redirect we pass ownership of the callback to the new channel.
+ mBodyCallback = aBodyCallback;
+ aBodyCallback = nullptr;
+
+ mSynthesizedCacheInfo = aSynthesizedCacheInfo;
+
+ if (!mSynthesizedResponseHead) {
+ mSynthesizedResponseHead.reset(new nsHttpResponseHead());
+ }
+
+ mResponseHead = std::move(mSynthesizedResponseHead);
+
+ if (ShouldRedirect()) {
+ rv = FollowSyntheticRedirect();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ // Intercepted responses should already be decoded.
+ SetApplyConversion(false);
+
+ // Errors and redirects may not have a body. Synthesize an empty string
+ // stream here so later code can be simpler.
+ mBodyReader = aBody;
+ if (!mBodyReader) {
+ rv = NS_NewCStringInputStream(getter_AddRefs(mBodyReader), ""_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIURI> responseURI;
+ if (!aFinalURLSpec.IsEmpty()) {
+ rv = NS_NewURI(getter_AddRefs(responseURI), aFinalURLSpec);
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ responseURI = mURI;
+ }
+
+ bool equal = false;
+ Unused << mURI->Equals(responseURI, &equal);
+ if (!equal) {
+ rv = RedirectForResponseURL(responseURI, aResponseRedirected);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+ rv = StartPump();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::FinishSynthesizedResponse() {
+ if (mCanceled) {
+ // Return NS_OK. The channel should fire callbacks with an error code
+ // if it was cancelled before this point.
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::CancelInterception(nsresult aStatus) {
+ return Cancel(aStatus);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetChannel(nsIChannel** aChannel) {
+ nsCOMPtr<nsIChannel> ref(this);
+ ref.forget(aChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetSecureUpgradedChannelURI(
+ nsIURI** aSecureUpgradedChannelURI) {
+ nsCOMPtr<nsIURI> ref(mURI);
+ ref.forget(aSecureUpgradedChannelURI);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetChannelInfo(
+ mozilla::dom::ChannelInfo* aChannelInfo) {
+ return aChannelInfo->ResurrectInfoOnChannel(this);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetInternalContentPolicyType(
+ nsContentPolicyType* aPolicyType) {
+ if (mLoadInfo) {
+ *aPolicyType = mLoadInfo->InternalContentPolicyType();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetConsoleReportCollector(
+ nsIConsoleReportCollector** aConsoleReportCollector) {
+ nsCOMPtr<nsIConsoleReportCollector> ref(this);
+ ref.forget(aConsoleReportCollector);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetFetchHandlerStart(TimeStamp aTimeStamp) {
+ mTimeStamps.RecordTime(std::move(aTimeStamp));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetFetchHandlerFinish(TimeStamp aTimeStamp) {
+ mTimeStamps.RecordTime(std::move(aTimeStamp));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetIsReset(bool* aResult) {
+ *aResult = mInterceptionReset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetReleaseHandle(nsISupports* aHandle) {
+ mReleaseHandle = aHandle;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::OnRedirectVerifyCallback(nsresult rv) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = OpenRedirectChannel();
+ }
+
+ nsCOMPtr<nsIRedirectResultListener> hook;
+ GetCallback(hook);
+ if (hook) {
+ hook->OnRedirectResult(rv);
+ }
+
+ if (NS_FAILED(rv)) {
+ Cancel(rv);
+ }
+
+ MaybeCallBodyCallback();
+
+ StoreIsPending(false);
+ // We can only release listeners after the redirected channel really owns
+ // mListener. Otherwise, the OnStart/OnStopRequest functions of mListener will
+ // not be called.
+ if (NS_SUCCEEDED(rv)) {
+ ReleaseListeners();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::OnStartRequest(nsIRequest* aRequest) {
+ INTERCEPTED_LOG(("InterceptedHttpChannel::OnStartRequest [%p]", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mProgressSink) {
+ GetCallback(mProgressSink);
+ }
+
+ MOZ_ASSERT_IF(!mLoadInfo->GetServiceWorkerTaintingSynthesized(),
+ mLoadInfo->GetLoadingPrincipal());
+ // No need to do ORB checks if these conditions hold.
+ MOZ_DIAGNOSTIC_ASSERT(mLoadInfo->GetServiceWorkerTaintingSynthesized() ||
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal());
+
+ if (mPump && mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
+ mPump->PeekStream(CallTypeSniffers, static_cast<nsIChannel*>(this));
+ }
+
+ nsresult rv = ProcessCrossOriginEmbedderPolicyHeader();
+ if (NS_FAILED(rv)) {
+ mStatus = NS_ERROR_BLOCKED_BY_POLICY;
+ Cancel(mStatus);
+ }
+
+ rv = ProcessCrossOriginResourcePolicyHeader();
+ if (NS_FAILED(rv)) {
+ mStatus = NS_ERROR_DOM_CORP_FAILED;
+ Cancel(mStatus);
+ }
+
+ rv = ComputeCrossOriginOpenerPolicyMismatch();
+ if (rv == NS_ERROR_BLOCKED_BY_POLICY) {
+ mStatus = NS_ERROR_BLOCKED_BY_POLICY;
+ Cancel(mStatus);
+ }
+
+ rv = ValidateMIMEType();
+ if (NS_FAILED(rv)) {
+ mStatus = rv;
+ Cancel(mStatus);
+ }
+
+ StoreOnStartRequestCalled(true);
+ if (mListener) {
+ return mListener->OnStartRequest(this);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) {
+ INTERCEPTED_LOG(("InterceptedHttpChannel::OnStopRequest [%p]", this));
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_SUCCEEDED(mStatus)) {
+ mStatus = aStatus;
+ }
+
+ MaybeCallBodyCallback();
+
+ mTimeStamps.RecordTime(InterceptionTimeStamps::Synthesized);
+
+ // Its possible that we have any async runnable queued to report some
+ // progress when OnStopRequest() is triggered. Report any left over
+ // progress immediately. The extra runnable will then do nothing thanks
+ // to the ReleaseListeners() call below.
+ MaybeCallStatusAndProgress();
+
+ StoreIsPending(false);
+
+ // Register entry to the PerformanceStorage resource timing
+ MaybeReportTimingData();
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ // These do allocations/frees/etc; avoid if not active
+ nsAutoCString requestMethod;
+ GetRequestMethod(requestMethod);
+
+ int32_t priority = PRIORITY_NORMAL;
+ GetPriority(&priority);
+
+ uint64_t size = 0;
+ GetEncodedBodySize(&size);
+
+ nsAutoCString contentType;
+ if (mResponseHead) {
+ mResponseHead->ContentType(contentType);
+ }
+ profiler_add_network_marker(
+ mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_STOP,
+ mLastStatusReported, TimeStamp::Now(), size, kCacheUnknown,
+ mLoadInfo->GetInnerWindowID(),
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0,
+ &mTransactionTimings, std::move(mSource),
+ Some(nsDependentCString(contentType.get())));
+ }
+
+ nsresult rv = NS_OK;
+ if (mListener) {
+ rv = mListener->OnStopRequest(this, mStatus);
+ }
+
+ gHttpHandler->OnStopRequest(this);
+
+ ReleaseListeners();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::OnDataAvailable(nsIRequest* aRequest,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) {
+ // Any thread if the channel has been retargeted.
+
+ if (mCanceled || !mListener) {
+ // If there is no listener, we still need to drain the stream in order
+ // maintain necko invariants.
+ uint32_t unused = 0;
+ aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &unused);
+ return mStatus;
+ }
+ if (mProgressSink) {
+ if (!(mLoadFlags & HttpBaseChannel::LOAD_BACKGROUND)) {
+ mProgress = aOffset + aCount;
+ MaybeCallStatusAndProgress();
+ }
+ }
+
+ return mListener->OnDataAvailable(this, aInputStream, aOffset, aCount);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::RetargetDeliveryTo(nsISerialEventTarget* aNewTarget) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_ARG(aNewTarget);
+
+ // If retargeting to the main thread, do nothing.
+ if (aNewTarget->IsOnCurrentThread()) {
+ return NS_OK;
+ }
+
+ // Retargeting is only valid during OnStartRequest for nsIChannels. So
+ // we should only be called if we have a pump.
+ if (!mPump) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mPump->RetargetDeliveryTo(aNewTarget);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) {
+ if (!mPump) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mPump->GetDeliveryTarget(aEventTarget);
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::CheckListenerChain() {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
+ do_QueryInterface(mListener, &rv);
+ if (retargetableListener) {
+ rv = retargetableListener->CheckListenerChain();
+ }
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// InterceptedHttpChannel::nsICacheInfoChannel
+//-----------------------------------------------------------------------------
+// InterceptedHttpChannel does not really implement the nsICacheInfoChannel
+// interface, we tranfers parameters to the saved
+// nsICacheInfoChannel(mSynthesizedCacheInfo) from StartSynthesizedResponse. And
+// we return false in IsFromCache and NS_ERROR_NOT_AVAILABLE for all other
+// methods while the saved mSynthesizedCacheInfo does not exist.
+NS_IMETHODIMP
+InterceptedHttpChannel::IsFromCache(bool* value) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->IsFromCache(value);
+ }
+ *value = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::IsRacing(bool* value) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->IsRacing(value);
+ }
+ *value = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetCacheEntryId(uint64_t* aCacheEntryId) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetCacheEntryId(aCacheEntryId);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetCacheTokenFetchCount(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetCacheTokenFetchCount(_retval);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetCacheTokenExpirationTime(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetCacheTokenExpirationTime(_retval);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetAllowStaleCacheContent(
+ bool aAllowStaleCacheContent) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->SetAllowStaleCacheContent(
+ aAllowStaleCacheContent);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetAllowStaleCacheContent(
+ bool* aAllowStaleCacheContent) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetAllowStaleCacheContent(
+ aAllowStaleCacheContent);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetForceValidateCacheContent(
+ bool aForceValidateCacheContent) {
+ // We store aForceValidateCacheContent locally because
+ // mSynthesizedCacheInfo isn't present until a response
+ // is actually synthesized, which is too late for the value
+ // to be forwarded during the redirect to the intercepted
+ // channel.
+ StoreForceValidateCacheContent(aForceValidateCacheContent);
+
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->SetForceValidateCacheContent(
+ aForceValidateCacheContent);
+ }
+ return NS_OK;
+}
+NS_IMETHODIMP
+InterceptedHttpChannel::GetForceValidateCacheContent(
+ bool* aForceValidateCacheContent) {
+ *aForceValidateCacheContent = LoadForceValidateCacheContent();
+#ifdef DEBUG
+ if (mSynthesizedCacheInfo) {
+ bool synthesizedForceValidateCacheContent;
+ mSynthesizedCacheInfo->GetForceValidateCacheContent(
+ &synthesizedForceValidateCacheContent);
+ MOZ_ASSERT(*aForceValidateCacheContent ==
+ synthesizedForceValidateCacheContent);
+ }
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetPreferCacheLoadOverBypass(
+ bool* aPreferCacheLoadOverBypass) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetPreferCacheLoadOverBypass(
+ aPreferCacheLoadOverBypass);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetPreferCacheLoadOverBypass(
+ bool aPreferCacheLoadOverBypass) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->SetPreferCacheLoadOverBypass(
+ aPreferCacheLoadOverBypass);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::PreferAlternativeDataType(
+ const nsACString& aType, const nsACString& aContentType,
+ PreferredAlternativeDataDeliveryType aDeliverAltData) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+ mPreferredCachedAltDataTypes.AppendElement(PreferredAlternativeDataTypeParams(
+ nsCString(aType), nsCString(aContentType), aDeliverAltData));
+ return NS_OK;
+}
+
+const nsTArray<PreferredAlternativeDataTypeParams>&
+InterceptedHttpChannel::PreferredAlternativeDataTypes() {
+ return mPreferredCachedAltDataTypes;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetAlternativeDataType(nsACString& aType) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetAlternativeDataType(aType);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::OpenAlternativeOutputStream(
+ const nsACString& type, int64_t predictedSize,
+ nsIAsyncOutputStream** _retval) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->OpenAlternativeOutputStream(
+ type, predictedSize, _retval);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetOriginalInputStream(
+ nsIInputStreamReceiver* aReceiver) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetOriginalInputStream(aReceiver);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetAlternativeDataInputStream(
+ nsIInputStream** aInputStream) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetAlternativeDataInputStream(aInputStream);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::GetCacheKey(uint32_t* key) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->GetCacheKey(key);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+InterceptedHttpChannel::SetCacheKey(uint32_t key) {
+ if (mSynthesizedCacheInfo) {
+ return mSynthesizedCacheInfo->SetCacheKey(key);
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+// InterceptionTimeStamps implementation
+InterceptedHttpChannel::InterceptionTimeStamps::InterceptionTimeStamps()
+ : mStage(InterceptedHttpChannel::InterceptionTimeStamps::InterceptionStart),
+ mStatus(InterceptedHttpChannel::InterceptionTimeStamps::Created) {}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::Init(
+ nsIChannel* aChannel) {
+ MOZ_ASSERT(aChannel);
+ MOZ_ASSERT(mStatus == Created);
+
+ mStatus = Initialized;
+
+ mIsNonSubresourceRequest = nsContentUtils::IsNonSubresourceRequest(aChannel);
+ mKey = mIsNonSubresourceRequest ? "navigation"_ns : "subresource"_ns;
+ nsCOMPtr<nsIInterceptedChannel> interceptedChannel =
+ do_QueryInterface(aChannel);
+ // It must be a InterceptedHttpChannel
+ MOZ_ASSERT(interceptedChannel);
+ if (!mIsNonSubresourceRequest) {
+ interceptedChannel->GetSubresourceTimeStampKey(aChannel, mSubresourceKey);
+ }
+}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::RecordTime(
+ InterceptedHttpChannel::InterceptionTimeStamps::Status&& aStatus,
+ TimeStamp&& aTimeStamp) {
+ // Only allow passing Synthesized, Reset, Redirected, and Canceled in this
+ // method.
+ MOZ_ASSERT(aStatus == Synthesized || aStatus == Reset ||
+ aStatus == Canceled || aStatus == Redirected);
+ if (mStatus == Canceled) {
+ return;
+ }
+
+ // If current status is not Initialized, only Canceled can be recorded.
+ // That means it is canceled after other operation is done, ex. synthesized.
+ MOZ_ASSERT(mStatus == Initialized || aStatus == Canceled);
+
+ switch (mStatus) {
+ case Initialized:
+ mStatus = aStatus;
+ break;
+ case Synthesized:
+ mStatus = CanceledAfterSynthesized;
+ break;
+ case Reset:
+ mStatus = CanceledAfterReset;
+ break;
+ case Redirected:
+ mStatus = CanceledAfterRedirected;
+ break;
+ // Channel is cancelled before calling AsyncOpenInternal(), no need to
+ // record the cancel time stamp.
+ case Created:
+ return;
+ default:
+ MOZ_ASSERT(false);
+ break;
+ }
+
+ RecordTimeInternal(std::move(aTimeStamp));
+}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::RecordTime(
+ TimeStamp&& aTimeStamp) {
+ MOZ_ASSERT(mStatus == Initialized || mStatus == Canceled);
+ if (mStatus == Canceled) {
+ return;
+ }
+ RecordTimeInternal(std::move(aTimeStamp));
+}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::RecordTimeInternal(
+ TimeStamp&& aTimeStamp) {
+ MOZ_ASSERT(mStatus != Created);
+
+ if (mStatus == Canceled && mStage != InterceptionFinish) {
+ mFetchHandlerStart = aTimeStamp;
+ mFetchHandlerFinish = aTimeStamp;
+ mStage = InterceptionFinish;
+ }
+
+ switch (mStage) {
+ case InterceptionStart: {
+ MOZ_ASSERT(mInterceptionStart.IsNull());
+ mInterceptionStart = aTimeStamp;
+ mStage = FetchHandlerStart;
+ break;
+ }
+ case (FetchHandlerStart): {
+ MOZ_ASSERT(mFetchHandlerStart.IsNull());
+ mFetchHandlerStart = aTimeStamp;
+ mStage = FetchHandlerFinish;
+ break;
+ }
+ case (FetchHandlerFinish): {
+ MOZ_ASSERT(mFetchHandlerFinish.IsNull());
+ mFetchHandlerFinish = aTimeStamp;
+ mStage = InterceptionFinish;
+ break;
+ }
+ case InterceptionFinish: {
+ mInterceptionFinish = aTimeStamp;
+ SaveTimeStamps();
+ return;
+ }
+ default: {
+ return;
+ }
+ }
+}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::GenKeysWithStatus(
+ nsCString& aKey, nsCString& aSubresourceKey) {
+ nsAutoCString statusString;
+ switch (mStatus) {
+ case Synthesized:
+ statusString = "synthesized"_ns;
+ break;
+ case Reset:
+ statusString = "reset"_ns;
+ break;
+ case Redirected:
+ statusString = "redirected"_ns;
+ break;
+ case Canceled:
+ statusString = "canceled"_ns;
+ break;
+ case CanceledAfterSynthesized:
+ statusString = "canceled-after-synthesized"_ns;
+ break;
+ case CanceledAfterReset:
+ statusString = "canceled-after-reset"_ns;
+ break;
+ case CanceledAfterRedirected:
+ statusString = "canceled-after-redirected"_ns;
+ break;
+ default:
+ return;
+ }
+ aKey = mKey;
+ aSubresourceKey = mSubresourceKey;
+ aKey.AppendLiteral("_");
+ aSubresourceKey.AppendLiteral("_");
+ aKey.Append(statusString);
+ aSubresourceKey.Append(statusString);
+}
+
+void InterceptedHttpChannel::InterceptionTimeStamps::SaveTimeStamps() {
+ MOZ_ASSERT(mStatus != Initialized && mStatus != Created);
+
+ if (mStatus == Synthesized || mStatus == Reset) {
+ Telemetry::HistogramID id =
+ Telemetry::SERVICE_WORKER_FETCH_EVENT_FINISH_SYNTHESIZED_RESPONSE_MS_2;
+ if (mStatus == Reset) {
+ id = Telemetry::SERVICE_WORKER_FETCH_EVENT_CHANNEL_RESET_MS_2;
+ }
+
+ Telemetry::Accumulate(
+ id, mKey,
+ static_cast<uint32_t>(
+ (mInterceptionFinish - mFetchHandlerFinish).ToMilliseconds()));
+ if (!mIsNonSubresourceRequest && !mSubresourceKey.IsEmpty()) {
+ Telemetry::Accumulate(
+ id, mSubresourceKey,
+ static_cast<uint32_t>(
+ (mInterceptionFinish - mFetchHandlerFinish).ToMilliseconds()));
+ }
+ }
+
+ if (!mFetchHandlerStart.IsNull()) {
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_EVENT_DISPATCH_MS_2, mKey,
+ static_cast<uint32_t>(
+ (mFetchHandlerStart - mInterceptionStart).ToMilliseconds()));
+
+ if (!mIsNonSubresourceRequest && !mSubresourceKey.IsEmpty()) {
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_EVENT_DISPATCH_MS_2, mSubresourceKey,
+ static_cast<uint32_t>(
+ (mFetchHandlerStart - mInterceptionStart).ToMilliseconds()));
+ }
+ }
+
+ nsAutoCString key, subresourceKey;
+ GenKeysWithStatus(key, subresourceKey);
+
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_INTERCEPTION_DURATION_MS_2, key,
+ static_cast<uint32_t>(
+ (mInterceptionFinish - mInterceptionStart).ToMilliseconds()));
+ if (!mIsNonSubresourceRequest && !mSubresourceKey.IsEmpty()) {
+ Telemetry::Accumulate(
+ Telemetry::SERVICE_WORKER_FETCH_INTERCEPTION_DURATION_MS_2,
+ subresourceKey,
+ static_cast<uint32_t>(
+ (mInterceptionFinish - mInterceptionStart).ToMilliseconds()));
+ }
+}
+
+} // namespace mozilla::net