diff options
Diffstat (limited to 'netwerk/base/nsAsyncRedirectVerifyHelper.cpp')
-rw-r--r-- | netwerk/base/nsAsyncRedirectVerifyHelper.cpp | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/netwerk/base/nsAsyncRedirectVerifyHelper.cpp b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp new file mode 100644 index 000000000..3b19b93c7 --- /dev/null +++ b/netwerk/base/nsAsyncRedirectVerifyHelper.cpp @@ -0,0 +1,288 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "mozilla/Logging.h" +#include "nsAsyncRedirectVerifyHelper.h" +#include "nsThreadUtils.h" +#include "nsNetUtil.h" + +#include "nsIOService.h" +#include "nsIChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsILoadInfo.h" + +namespace mozilla { +namespace net { + +static LazyLogModule gRedirectLog("nsRedirect"); +#undef LOG +#define LOG(args) MOZ_LOG(gRedirectLog, LogLevel::Debug, args) + +NS_IMPL_ISUPPORTS(nsAsyncRedirectVerifyHelper, + nsIAsyncVerifyRedirectCallback, + nsIRunnable) + +class nsAsyncVerifyRedirectCallbackEvent : public Runnable { +public: + nsAsyncVerifyRedirectCallbackEvent(nsIAsyncVerifyRedirectCallback *cb, + nsresult result) + : mCallback(cb), mResult(result) { + } + + NS_IMETHOD Run() override + { + LOG(("nsAsyncVerifyRedirectCallbackEvent::Run() " + "callback to %p with result %x", + mCallback.get(), mResult)); + (void) mCallback->OnRedirectVerifyCallback(mResult); + return NS_OK; + } +private: + nsCOMPtr<nsIAsyncVerifyRedirectCallback> mCallback; + nsresult mResult; +}; + +nsAsyncRedirectVerifyHelper::nsAsyncRedirectVerifyHelper() + : mFlags(0), + mWaitingForRedirectCallback(false), + mCallbackInitiated(false), + mExpectedCallbacks(0), + mResult(NS_OK) +{ +} + +nsAsyncRedirectVerifyHelper::~nsAsyncRedirectVerifyHelper() +{ + NS_ASSERTION(NS_FAILED(mResult) || mExpectedCallbacks == 0, + "Did not receive all required callbacks!"); +} + +nsresult +nsAsyncRedirectVerifyHelper::Init(nsIChannel* oldChan, nsIChannel* newChan, + uint32_t flags, bool synchronize) +{ + LOG(("nsAsyncRedirectVerifyHelper::Init() " + "oldChan=%p newChan=%p", oldChan, newChan)); + mOldChan = oldChan; + mNewChan = newChan; + mFlags = flags; + mCallbackThread = do_GetCurrentThread(); + + if (!(flags & (nsIChannelEventSink::REDIRECT_INTERNAL | + nsIChannelEventSink::REDIRECT_STS_UPGRADE))) { + nsCOMPtr<nsILoadInfo> loadInfo = oldChan->GetLoadInfo(); + if (loadInfo && loadInfo->GetDontFollowRedirects()) { + ExplicitCallback(NS_BINDING_ABORTED); + return NS_OK; + } + } + + if (synchronize) + mWaitingForRedirectCallback = true; + + nsresult rv; + rv = NS_DispatchToMainThread(this); + NS_ENSURE_SUCCESS(rv, rv); + + if (synchronize) { + nsIThread *thread = NS_GetCurrentThread(); + while (mWaitingForRedirectCallback) { + if (!NS_ProcessNextEvent(thread)) { + return NS_ERROR_UNEXPECTED; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback(nsresult result) +{ + LOG(("nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback() " + "result=%x expectedCBs=%u mResult=%x", + result, mExpectedCallbacks, mResult)); + + MOZ_DIAGNOSTIC_ASSERT(mExpectedCallbacks > 0, + "OnRedirectVerifyCallback called more times than expected"); + if (mExpectedCallbacks <= 0) { + return NS_ERROR_UNEXPECTED; + } + + --mExpectedCallbacks; + + // If response indicates failure we may call back immediately + if (NS_FAILED(result)) { + // We chose to store the first failure-value (as opposed to the last) + if (NS_SUCCEEDED(mResult)) + mResult = result; + + // If InitCallback() has been called, just invoke the callback and + // return. Otherwise it will be invoked from InitCallback() + if (mCallbackInitiated) { + ExplicitCallback(mResult); + return NS_OK; + } + } + + // If the expected-counter is in balance and InitCallback() was called, all + // sinks have agreed that the redirect is ok and we can invoke our callback + if (mCallbackInitiated && mExpectedCallbacks == 0) { + ExplicitCallback(mResult); + } + + return NS_OK; +} + +nsresult +nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect(nsIChannelEventSink *sink, + nsIChannel *oldChannel, + nsIChannel *newChannel, + uint32_t flags) +{ + LOG(("nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect() " + "sink=%p expectedCBs=%u mResult=%x", + sink, mExpectedCallbacks, mResult)); + + ++mExpectedCallbacks; + + if (IsOldChannelCanceled()) { + LOG((" old channel has been canceled, cancel the redirect by " + "emulating OnRedirectVerifyCallback...")); + (void) OnRedirectVerifyCallback(NS_BINDING_ABORTED); + return NS_BINDING_ABORTED; + } + + nsresult rv = + sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this); + + LOG((" result=%x expectedCBs=%u", rv, mExpectedCallbacks)); + + // If the sink returns failure from this call the redirect is vetoed. We + // emulate a callback from the sink in this case in order to perform all + // the necessary logic. + if (NS_FAILED(rv)) { + LOG((" emulating OnRedirectVerifyCallback...")); + (void) OnRedirectVerifyCallback(rv); + } + + return rv; // Return the actual status since our caller may need it +} + +void +nsAsyncRedirectVerifyHelper::ExplicitCallback(nsresult result) +{ + LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "result=%x expectedCBs=%u mCallbackInitiated=%u mResult=%x", + result, mExpectedCallbacks, mCallbackInitiated, mResult)); + + nsCOMPtr<nsIAsyncVerifyRedirectCallback> + callback(do_QueryInterface(mOldChan)); + + if (!callback || !mCallbackThread) { + LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "callback=%p mCallbackThread=%p", callback.get(), mCallbackThread.get())); + return; + } + + mCallbackInitiated = false; // reset to ensure only one callback + mWaitingForRedirectCallback = false; + + // Now, dispatch the callback on the event-target which called Init() + nsCOMPtr<nsIRunnable> event = + new nsAsyncVerifyRedirectCallbackEvent(callback, result); + if (!event) { + NS_WARNING("nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "failed creating callback event!"); + return; + } + nsresult rv = mCallbackThread->Dispatch(event, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING("nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "failed dispatching callback event!"); + } else { + LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() " + "dispatched callback event=%p", event.get())); + } + +} + +void +nsAsyncRedirectVerifyHelper::InitCallback() +{ + LOG(("nsAsyncRedirectVerifyHelper::InitCallback() " + "expectedCBs=%d mResult=%x", mExpectedCallbacks, mResult)); + + mCallbackInitiated = true; + + // Invoke the callback if we are done + if (mExpectedCallbacks == 0) + ExplicitCallback(mResult); +} + +NS_IMETHODIMP +nsAsyncRedirectVerifyHelper::Run() +{ + /* If the channel got canceled after it fired AsyncOnChannelRedirect + * and before we got here, mostly because docloader load has been canceled, + * we must completely ignore this notification and prevent any further + * notification. + */ + if (IsOldChannelCanceled()) { + ExplicitCallback(NS_BINDING_ABORTED); + return NS_OK; + } + + // First, the global observer + NS_ASSERTION(gIOService, "Must have an IO service at this point"); + LOG(("nsAsyncRedirectVerifyHelper::Run() calling gIOService...")); + nsresult rv = gIOService->AsyncOnChannelRedirect(mOldChan, mNewChan, + mFlags, this); + if (NS_FAILED(rv)) { + ExplicitCallback(rv); + return NS_OK; + } + + // Now, the per-channel observers + nsCOMPtr<nsIChannelEventSink> sink; + NS_QueryNotificationCallbacks(mOldChan, sink); + if (sink) { + LOG(("nsAsyncRedirectVerifyHelper::Run() calling sink...")); + rv = DelegateOnChannelRedirect(sink, mOldChan, mNewChan, mFlags); + } + + // All invocations to AsyncOnChannelRedirect has been done - call + // InitCallback() to flag this + InitCallback(); + return NS_OK; +} + +bool +nsAsyncRedirectVerifyHelper::IsOldChannelCanceled() +{ + bool canceled; + nsCOMPtr<nsIHttpChannelInternal> oldChannelInternal = + do_QueryInterface(mOldChan); + if (oldChannelInternal) { + oldChannelInternal->GetCanceled(&canceled); + if (canceled) { + return true; + } + } else if (mOldChan) { + // For non-HTTP channels check on the status, failure + // indicates the channel has probably been canceled. + nsresult status = NS_ERROR_FAILURE; + mOldChan->GetStatus(&status); + if (NS_FAILED(status)) { + return true; + } + } + + return false; +} + +} // namespace net +} // namespace mozilla |