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