Source code

Revision control

Other Tools

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