/* -*- 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 "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 "nsIRedirectResultListener.h" #include "nsStringStream.h" #include "nsStreamUtils.h" namespace mozilla { namespace net { NS_IMPL_ISUPPORTS_INHERITED(InterceptedHttpChannel, HttpBaseChannel, nsIInterceptedChannel, nsICacheInfoChannel, nsIAsyncVerifyRedirectCallback, nsIRequestObserver, nsIStreamListener, nsIThreadRetargetableRequest, nsIThreadRetargetableStreamListener) InterceptedHttpChannel::InterceptedHttpChannel( PRTime aCreationTime, const TimeStamp& aCreationTimestamp, const TimeStamp& aAsyncOpenTimestamp) : HttpAsyncAborter(this), mProgress(0), mProgressReported(0), mSynthesizedStreamLength(-1), mResumeStartPos(0), mSynthesizedOrReset(Invalid), mCallingStatusAndProgress(false) { // 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. mChannelCreationTime = aCreationTime; mChannelCreationTimestamp = aCreationTimestamp; 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) { 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 resumable = do_QueryInterface(aChannel); if (!resumable) { return NS_ERROR_NOT_RESUMABLE; } resumable->ResumeAt(mResumeStartPos, mResumeEntityId); } return NS_OK; } void InterceptedHttpChannel::AsyncOpenInternal() { // 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; // 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); } }); 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(); if (NS_WARN_IF(NS_FAILED(rv))) { Cancel(rv); } } }); // Otherwise we need to trigger a FetchEvent in a ServiceWorker. nsCOMPtr 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 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 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 newChannel; nsCOMPtr 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); } 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 bodyCallback = std::move(mBodyCallback); RefPtr 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 redirectLoadInfo = CloneLoadInfoForRedirect(aResponseURI, flags); ExtContentPolicyType contentPolicyType = redirectLoadInfo->GetExternalContentPolicyType(); rv = newChannel->Init( aResponseURI, mCaps, static_cast(mProxyInfo.get()), mProxyResolveFlags, mProxyURI, mChannelId, contentPolicyType); newChannel->SetLoadInfo(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() { 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 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 callback = std::move(mBodyCallback); if (callback) { callback->BodyComplete(mStatus); } } // static already_AddRefed InterceptedHttpChannel::CreateForInterception( PRTime aCreationTime, const TimeStamp& aCreationTimestamp, const TimeStamp& aAsyncOpenTimestamp) { // Create an InterceptedHttpChannel that will trigger a FetchEvent // in a ServiceWorker when opened. RefPtr ref = new InterceptedHttpChannel( aCreationTime, aCreationTimestamp, aAsyncOpenTimestamp); return ref.forget(); } // static already_AddRefed 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 ref = new InterceptedHttpChannel( aCreationTime, aCreationTimestamp, aAsyncOpenTimestamp); ref->mResponseHead = MakeUnique(*aHead); ref->mBodyReader = aBody; ref->mBodyCallback = aBodyCallback; return ref.forget(); } NS_IMETHODIMP InterceptedHttpChannel::Cancel(nsresult aStatus) { // 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; } mCanceled = true; 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(nsISupports** aSecurityInfo) { nsCOMPtr ref(mSecurityInfo); ref.forget(aSecurityInfo); return NS_OK; } NS_IMETHODIMP InterceptedHttpChannel::AsyncOpen(nsIStreamListener* aListener) { nsCOMPtr 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) { // 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::SetupFallbackChannel(const char* aFallbackKey) { // AppCache should not be used with service worker intercepted channels. // This should never be called. 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(aPriority, INT16_MIN, INT16_MAX); return NS_OK; } NS_IMETHODIMP InterceptedHttpChannel::SetClassFlags(uint32_t aClassFlags) { mClassOfService = aClassFlags; return NS_OK; } NS_IMETHODIMP InterceptedHttpChannel::ClearClassFlags(uint32_t aClassFlags) { mClassOfService &= ~aClassFlags; return NS_OK; } NS_IMETHODIMP InterceptedHttpChannel::AddClassFlags(uint32_t aClassFlags) { mClassOfService |= aClassFlags; 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); } NS_IMETHODIMP InterceptedHttpChannel::ResetInterception(void) { if (mCanceled) { return mStatus; } uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL; nsCOMPtr newChannel; nsCOMPtr redirectLoadInfo = CloneLoadInfoForRedirect(mURI, flags); nsresult rv = NS_NewChannelInternal(getter_AddRefs(newChannel), mURI, redirectLoadInfo, nullptr, // PerformanceStorage nullptr, // aLoadGroup nullptr, // aCallbacks mLoadFlags); NS_ENSURE_SUCCESS(rv, rv); rv = SetupReplacementChannel(mURI, newChannel, true, flags); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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); } 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 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; } // TODO: Remove this API after interception moves to the parent process in // e10s mode. return NS_OK; } NS_IMETHODIMP InterceptedHttpChannel::CancelInterception(nsresult aStatus) { return Cancel(aStatus); } NS_IMETHODIMP InterceptedHttpChannel::GetChannel(nsIChannel** aChannel) { nsCOMPtr ref(this); ref.forget(aChannel); return NS_OK; } NS_IMETHODIMP InterceptedHttpChannel::GetSecureUpgradedChannelURI( nsIURI** aSecureUpgradedChannelURI) { nsCOMPtr 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 ref(this); ref.forget(aConsoleReportCollector); return NS_OK; } NS_IMETHODIMP InterceptedHttpChannel::GetLaunchServiceWorkerStart( mozilla::TimeStamp* aTimeStamp) { return HttpBaseChannel::GetLaunchServiceWorkerStart(aTimeStamp); } NS_IMETHODIMP InterceptedHttpChannel::SetLaunchServiceWorkerStart( mozilla::TimeStamp aTimeStamp) { return HttpBaseChannel::SetLaunchServiceWorkerStart(aTimeStamp); } NS_IMETHODIMP InterceptedHttpChannel::GetLaunchServiceWorkerEnd( mozilla::TimeStamp* aTimeStamp) { return HttpBaseChannel::GetLaunchServiceWorkerEnd(aTimeStamp); } NS_IMETHODIMP InterceptedHttpChannel::SetLaunchServiceWorkerEnd( mozilla::TimeStamp aTimeStamp) { return HttpBaseChannel::SetLaunchServiceWorkerEnd(aTimeStamp); } NS_IMETHODIMP InterceptedHttpChannel::SetDispatchFetchEventStart( mozilla::TimeStamp aTimeStamp) { return HttpBaseChannel::SetDispatchFetchEventStart(aTimeStamp); } NS_IMETHODIMP InterceptedHttpChannel::SetDispatchFetchEventEnd( mozilla::TimeStamp aTimeStamp) { return HttpBaseChannel::SetDispatchFetchEventEnd(aTimeStamp); } NS_IMETHODIMP InterceptedHttpChannel::SetHandleFetchEventStart( mozilla::TimeStamp aTimeStamp) { return HttpBaseChannel::SetHandleFetchEventStart(aTimeStamp); } NS_IMETHODIMP InterceptedHttpChannel::SetHandleFetchEventEnd(mozilla::TimeStamp aTimeStamp) { return HttpBaseChannel::SetHandleFetchEventEnd(aTimeStamp); } NS_IMETHODIMP InterceptedHttpChannel::SetFinishResponseStart(mozilla::TimeStamp aTimeStamp) { mFinishResponseStart = aTimeStamp; return NS_OK; } NS_IMETHODIMP InterceptedHttpChannel::SetFinishSynthesizedResponseEnd( mozilla::TimeStamp aTimeStamp) { MOZ_ASSERT(mSynthesizedOrReset == Invalid); mSynthesizedOrReset = Synthesized; mFinishResponseEnd = aTimeStamp; return NS_OK; } NS_IMETHODIMP InterceptedHttpChannel::SetChannelResetEnd(mozilla::TimeStamp aTimeStamp) { MOZ_ASSERT(mSynthesizedOrReset == Invalid); mSynthesizedOrReset = Reset; mFinishResponseEnd = aTimeStamp; return NS_OK; } NS_IMETHODIMP InterceptedHttpChannel::SaveTimeStamps(void) { // If we were not able to start the fetch event for some reason (like // corrupted scripts), then just do nothing here. if (mHandleFetchEventStart.IsNull()) { return NS_OK; } bool isNonSubresourceRequest = nsContentUtils::IsNonSubresourceRequest(this); nsCString navigationOrSubresource = isNonSubresourceRequest ? "navigation"_ns : "subresource"_ns; nsAutoCString subresourceKey(""_ns); GetSubresourceTimeStampKey(this, 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()) { 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( (mFinishResponseEnd - mFinishResponseStart).ToMilliseconds())); if (!isNonSubresourceRequest && !subresourceKey.IsEmpty()) { Telemetry::Accumulate( id, subresourceKey, static_cast( (mFinishResponseEnd - mFinishResponseStart).ToMilliseconds())); } } Telemetry::Accumulate( Telemetry::SERVICE_WORKER_FETCH_EVENT_DISPATCH_MS, navigationOrSubresource, static_cast((mHandleFetchEventStart - mDispatchFetchEventStart) .ToMilliseconds())); if (!isNonSubresourceRequest && !subresourceKey.IsEmpty()) { Telemetry::Accumulate(Telemetry::SERVICE_WORKER_FETCH_EVENT_DISPATCH_MS, subresourceKey, static_cast((mHandleFetchEventStart - mDispatchFetchEventStart) .ToMilliseconds())); } if (!mFinishResponseEnd.IsNull()) { Telemetry::Accumulate( Telemetry::SERVICE_WORKER_FETCH_INTERCEPTION_DURATION_MS, navigationOrSubresource, static_cast( (mFinishResponseEnd - mDispatchFetchEventStart).ToMilliseconds())); if (!isNonSubresourceRequest && !subresourceKey.IsEmpty()) { Telemetry::Accumulate( Telemetry::SERVICE_WORKER_FETCH_INTERCEPTION_DURATION_MS, subresourceKey, static_cast((mFinishResponseEnd - mDispatchFetchEventStart) .ToMilliseconds())); } } 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 hook; GetCallback(hook); if (hook) { hook->OnRedirectResult(NS_SUCCEEDED(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) { MOZ_ASSERT(NS_IsMainThread()); if (!mProgressSink) { GetCallback(mProgressSink); } if (mPump && mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) { mPump->PeekStream(CallTypeSniffers, static_cast(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) { MOZ_ASSERT(NS_IsMainThread()); if (NS_SUCCEEDED(mStatus)) { mStatus = aStatus; } MaybeCallBodyCallback(); // 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(); 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(nsIEventTarget* 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(nsIEventTarget** 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 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(int32_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::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, bool aDeliverAltData) { ENSURE_CALLED_BEFORE_ASYNC_OPEN(); mPreferredCachedAltDataTypes.AppendElement(PreferredAlternativeDataTypeParams( nsCString(aType), nsCString(aContentType), aDeliverAltData)); return NS_OK; } const nsTArray& 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::GetAltDataInputStream( const nsACString& aType, nsIInputStreamReceiver* aReceiver) { if (mSynthesizedCacheInfo) { return mSynthesizedCacheInfo->GetAltDataInputStream(aType, aReceiver); } 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; } } // namespace net } // namespace mozilla