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