tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsAsyncRedirectVerifyHelper.cpp (9143B)


      1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "mozilla/Logging.h"
      7 #include "mozilla/SpinEventLoopUntil.h"
      8 #include "nsAsyncRedirectVerifyHelper.h"
      9 #include "nsThreadUtils.h"
     10 #include "nsNetUtil.h"
     11 
     12 #include "nsIOService.h"
     13 #include "nsIChannel.h"
     14 #include "nsIHttpChannelInternal.h"
     15 #include "nsIAsyncVerifyRedirectCallback.h"
     16 #include "nsILoadInfo.h"
     17 
     18 namespace mozilla {
     19 namespace net {
     20 
     21 static LazyLogModule gRedirectLog("nsRedirect");
     22 #undef LOG
     23 #define LOG(args) MOZ_LOG(gRedirectLog, LogLevel::Debug, args)
     24 
     25 NS_IMPL_ISUPPORTS(nsAsyncRedirectVerifyHelper, nsIAsyncVerifyRedirectCallback,
     26                  nsIRunnable, nsINamed)
     27 
     28 class nsAsyncVerifyRedirectCallbackEvent : public Runnable {
     29 public:
     30  nsAsyncVerifyRedirectCallbackEvent(nsIAsyncVerifyRedirectCallback* cb,
     31                                     nsresult result)
     32      : Runnable("nsAsyncVerifyRedirectCallbackEvent"),
     33        mCallback(cb),
     34        mResult(result) {}
     35 
     36  NS_IMETHOD Run() override {
     37    LOG(
     38        ("nsAsyncVerifyRedirectCallbackEvent::Run() "
     39         "callback to %p with result %" PRIx32,
     40         mCallback.get(), static_cast<uint32_t>(mResult)));
     41    (void)mCallback->OnRedirectVerifyCallback(mResult);
     42    return NS_OK;
     43  }
     44 
     45 private:
     46  nsCOMPtr<nsIAsyncVerifyRedirectCallback> mCallback;
     47  nsresult mResult;
     48 };
     49 
     50 nsAsyncRedirectVerifyHelper::~nsAsyncRedirectVerifyHelper() {
     51  NS_ASSERTION(NS_FAILED(mResult) || mExpectedCallbacks == 0,
     52               "Did not receive all required callbacks!");
     53 }
     54 
     55 nsresult nsAsyncRedirectVerifyHelper::Init(
     56    nsIChannel* oldChan, nsIChannel* newChan, uint32_t flags,
     57    nsIEventTarget* mainThreadEventTarget, bool synchronize) {
     58  LOG(
     59      ("nsAsyncRedirectVerifyHelper::Init() "
     60       "oldChan=%p newChan=%p",
     61       oldChan, newChan));
     62  mOldChan = oldChan;
     63  mNewChan = newChan;
     64  mFlags = flags;
     65  mCallbackEventTarget = NS_IsMainThread() && mainThreadEventTarget
     66                             ? mainThreadEventTarget
     67                             : GetCurrentSerialEventTarget();
     68 
     69  if (!(flags & (nsIChannelEventSink::REDIRECT_INTERNAL |
     70                 nsIChannelEventSink::REDIRECT_STS_UPGRADE |
     71                 nsIChannelEventSink::REDIRECT_TRANSPARENT))) {
     72    nsCOMPtr<nsILoadInfo> loadInfo = oldChan->LoadInfo();
     73    if (loadInfo->GetDontFollowRedirects()) {
     74      ExplicitCallback(NS_BINDING_ABORTED);
     75      return NS_OK;
     76    }
     77  }
     78 
     79  if (synchronize) mWaitingForRedirectCallback = true;
     80 
     81  nsCOMPtr<nsIRunnable> runnable = this;
     82  nsresult rv;
     83  rv = mainThreadEventTarget
     84           ? mainThreadEventTarget->Dispatch(runnable.forget())
     85           : GetMainThreadSerialEventTarget()->Dispatch(runnable.forget());
     86  NS_ENSURE_SUCCESS(rv, rv);
     87 
     88  if (synchronize) {
     89    if (!SpinEventLoopUntil("nsAsyncRedirectVerifyHelper::Init"_ns,
     90                            [&]() { return !mWaitingForRedirectCallback; })) {
     91      return NS_ERROR_UNEXPECTED;
     92    }
     93  }
     94 
     95  return NS_OK;
     96 }
     97 
     98 NS_IMETHODIMP
     99 nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback(nsresult result) {
    100  LOG(
    101      ("nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback() "
    102       "result=%" PRIx32 " expectedCBs=%u mResult=%" PRIx32,
    103       static_cast<uint32_t>(result), mExpectedCallbacks,
    104       static_cast<uint32_t>(mResult)));
    105 
    106  MOZ_DIAGNOSTIC_ASSERT(
    107      mExpectedCallbacks > 0,
    108      "OnRedirectVerifyCallback called more times than expected");
    109  if (mExpectedCallbacks <= 0) {
    110    return NS_ERROR_UNEXPECTED;
    111  }
    112 
    113  --mExpectedCallbacks;
    114 
    115  // If response indicates failure we may call back immediately
    116  if (NS_FAILED(result)) {
    117    // We chose to store the first failure-value (as opposed to the last)
    118    if (NS_SUCCEEDED(mResult)) mResult = result;
    119 
    120    // If InitCallback() has been called, just invoke the callback and
    121    // return. Otherwise it will be invoked from InitCallback()
    122    if (mCallbackInitiated) {
    123      ExplicitCallback(mResult);
    124      return NS_OK;
    125    }
    126  }
    127 
    128  // If the expected-counter is in balance and InitCallback() was called, all
    129  // sinks have agreed that the redirect is ok and we can invoke our callback
    130  if (mCallbackInitiated && mExpectedCallbacks == 0) {
    131    ExplicitCallback(mResult);
    132  }
    133 
    134  return NS_OK;
    135 }
    136 
    137 nsresult nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect(
    138    nsIChannelEventSink* sink, nsIChannel* oldChannel, nsIChannel* newChannel,
    139    uint32_t flags) {
    140  LOG(
    141      ("nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect() "
    142       "sink=%p expectedCBs=%u mResult=%" PRIx32,
    143       sink, mExpectedCallbacks, static_cast<uint32_t>(mResult)));
    144 
    145  ++mExpectedCallbacks;
    146 
    147  if (IsOldChannelCanceled()) {
    148    LOG(
    149        ("  old channel has been canceled, cancel the redirect by "
    150         "emulating OnRedirectVerifyCallback..."));
    151    (void)OnRedirectVerifyCallback(NS_BINDING_ABORTED);
    152    return NS_BINDING_ABORTED;
    153  }
    154 
    155  nsresult rv =
    156      sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
    157 
    158  LOG(("  result=%" PRIx32 " expectedCBs=%u", static_cast<uint32_t>(rv),
    159       mExpectedCallbacks));
    160 
    161  // If the sink returns failure from this call the redirect is vetoed. We
    162  // emulate a callback from the sink in this case in order to perform all
    163  // the necessary logic.
    164  if (NS_FAILED(rv)) {
    165    LOG(("  emulating OnRedirectVerifyCallback..."));
    166    (void)OnRedirectVerifyCallback(rv);
    167  }
    168 
    169  return rv;  // Return the actual status since our caller may need it
    170 }
    171 
    172 void nsAsyncRedirectVerifyHelper::ExplicitCallback(nsresult result) {
    173  LOG(
    174      ("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
    175       "result=%" PRIx32
    176       " expectedCBs=%u mCallbackInitiated=%u mResult=%" PRIx32,
    177       static_cast<uint32_t>(result), mExpectedCallbacks, mCallbackInitiated,
    178       static_cast<uint32_t>(mResult)));
    179 
    180  nsCOMPtr<nsIAsyncVerifyRedirectCallback> callback(
    181      do_QueryInterface(mOldChan));
    182 
    183  if (!callback || !mCallbackEventTarget) {
    184    LOG(
    185        ("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
    186         "callback=%p mCallbackEventTarget=%p",
    187         callback.get(), mCallbackEventTarget.get()));
    188    return;
    189  }
    190 
    191  mCallbackInitiated = false;  // reset to ensure only one callback
    192  mWaitingForRedirectCallback = false;
    193 
    194  // Now, dispatch the callback on the event-target which called Init()
    195  nsCOMPtr<nsIRunnable> event =
    196      new nsAsyncVerifyRedirectCallbackEvent(callback, result);
    197  if (!event) {
    198    NS_WARNING(
    199        "nsAsyncRedirectVerifyHelper::ExplicitCallback() "
    200        "failed creating callback event!");
    201    return;
    202  }
    203  nsresult rv = mCallbackEventTarget->Dispatch(event, NS_DISPATCH_NORMAL);
    204  if (NS_FAILED(rv)) {
    205    NS_WARNING(
    206        "nsAsyncRedirectVerifyHelper::ExplicitCallback() "
    207        "failed dispatching callback event!");
    208  } else {
    209    LOG(
    210        ("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
    211         "dispatched callback event=%p",
    212         event.get()));
    213  }
    214 }
    215 
    216 void nsAsyncRedirectVerifyHelper::InitCallback() {
    217  LOG(
    218      ("nsAsyncRedirectVerifyHelper::InitCallback() "
    219       "expectedCBs=%d mResult=%" PRIx32,
    220       mExpectedCallbacks, static_cast<uint32_t>(mResult)));
    221 
    222  mCallbackInitiated = true;
    223 
    224  // Invoke the callback if we are done
    225  if (mExpectedCallbacks == 0) ExplicitCallback(mResult);
    226 }
    227 
    228 NS_IMETHODIMP
    229 nsAsyncRedirectVerifyHelper::GetName(nsACString& aName) {
    230  aName.AssignLiteral("nsAsyncRedirectVerifyHelper");
    231  return NS_OK;
    232 }
    233 
    234 NS_IMETHODIMP
    235 nsAsyncRedirectVerifyHelper::Run() {
    236  /* If the channel got canceled after it fired AsyncOnChannelRedirect
    237   * and before we got here, mostly because docloader load has been canceled,
    238   * we must completely ignore this notification and prevent any further
    239   * notification.
    240   */
    241  if (IsOldChannelCanceled()) {
    242    ExplicitCallback(NS_BINDING_ABORTED);
    243    return NS_OK;
    244  }
    245 
    246  // If transparent, avoid notifying the observers.
    247  if (mFlags & nsIChannelEventSink::REDIRECT_TRANSPARENT) {
    248    ExplicitCallback(NS_OK);
    249    return NS_OK;
    250  }
    251 
    252  // First, the global observer
    253  NS_ASSERTION(gIOService, "Must have an IO service at this point");
    254  LOG(("nsAsyncRedirectVerifyHelper::Run() calling gIOService..."));
    255  nsresult rv =
    256      gIOService->AsyncOnChannelRedirect(mOldChan, mNewChan, mFlags, this);
    257  if (NS_FAILED(rv)) {
    258    ExplicitCallback(rv);
    259    return NS_OK;
    260  }
    261 
    262  // Now, the per-channel observers
    263  nsCOMPtr<nsIChannelEventSink> sink;
    264  NS_QueryNotificationCallbacks(mOldChan, sink);
    265  if (sink) {
    266    LOG(("nsAsyncRedirectVerifyHelper::Run() calling sink..."));
    267    rv = DelegateOnChannelRedirect(sink, mOldChan, mNewChan, mFlags);
    268  }
    269 
    270  // All invocations to AsyncOnChannelRedirect has been done - call
    271  // InitCallback() to flag this
    272  InitCallback();
    273  return NS_OK;
    274 }
    275 
    276 bool nsAsyncRedirectVerifyHelper::IsOldChannelCanceled() {
    277  if (!mOldChan) {
    278    return false;
    279  }
    280  bool canceled;
    281  nsresult rv = mOldChan->GetCanceled(&canceled);
    282  return NS_SUCCEEDED(rv) && canceled;
    283 }
    284 
    285 }  // namespace net
    286 }  // namespace mozilla