1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org networking code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Mozilla Corporation
19 : * Portions created by the Initial Developer are Copyright (C) 2009
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Honza Bambas <honzab@firemni.cz>
24 : * Bjarne Geir Herland <bjarne@runitsoft.com>
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either the GNU General Public License Version 2 or later (the "GPL"), or
28 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #ifdef MOZ_LOGGING
41 : #define FORCE_PR_LOG
42 : #endif
43 :
44 : #include "prlog.h"
45 : #include "nsAsyncRedirectVerifyHelper.h"
46 : #include "nsThreadUtils.h"
47 : #include "nsNetUtil.h"
48 :
49 : #include "nsIOService.h"
50 : #include "nsIChannel.h"
51 : #include "nsIHttpChannelInternal.h"
52 : #include "nsIAsyncVerifyRedirectCallback.h"
53 :
54 : #ifdef PR_LOGGING
55 1464 : static PRLogModuleInfo *gLog = PR_NewLogModule("nsRedirect");
56 : #define LOG(args) PR_LOG(gLog, PR_LOG_DEBUG, args)
57 : #else
58 : #define LOG(args)
59 : #endif
60 :
61 1703 : NS_IMPL_THREADSAFE_ISUPPORTS2(nsAsyncRedirectVerifyHelper,
62 : nsIAsyncVerifyRedirectCallback,
63 : nsIRunnable)
64 :
65 620 : class nsAsyncVerifyRedirectCallbackEvent : public nsRunnable {
66 : public:
67 155 : nsAsyncVerifyRedirectCallbackEvent(nsIAsyncVerifyRedirectCallback *cb,
68 : nsresult result)
69 155 : : mCallback(cb), mResult(result) {
70 155 : }
71 :
72 155 : NS_IMETHOD Run()
73 : {
74 155 : LOG(("nsAsyncVerifyRedirectCallbackEvent::Run() "
75 : "callback to %p with result %x",
76 : mCallback.get(), mResult));
77 155 : (void) mCallback->OnRedirectVerifyCallback(mResult);
78 155 : return NS_OK;
79 : }
80 : private:
81 : nsCOMPtr<nsIAsyncVerifyRedirectCallback> mCallback;
82 : nsresult mResult;
83 : };
84 :
85 155 : nsAsyncRedirectVerifyHelper::nsAsyncRedirectVerifyHelper()
86 : : mCallbackInitiated(false),
87 : mExpectedCallbacks(0),
88 155 : mResult(NS_OK)
89 : {
90 155 : }
91 :
92 310 : nsAsyncRedirectVerifyHelper::~nsAsyncRedirectVerifyHelper()
93 : {
94 155 : NS_ASSERTION(NS_FAILED(mResult) || mExpectedCallbacks == 0,
95 : "Did not receive all required callbacks!");
96 155 : }
97 :
98 : nsresult
99 155 : nsAsyncRedirectVerifyHelper::Init(nsIChannel* oldChan, nsIChannel* newChan,
100 : PRUint32 flags, bool synchronize)
101 : {
102 155 : LOG(("nsAsyncRedirectVerifyHelper::Init() "
103 : "oldChan=%p newChan=%p", oldChan, newChan));
104 155 : mOldChan = oldChan;
105 155 : mNewChan = newChan;
106 155 : mFlags = flags;
107 155 : mCallbackThread = do_GetCurrentThread();
108 :
109 155 : if (synchronize)
110 0 : mWaitingForRedirectCallback = true;
111 :
112 : nsresult rv;
113 155 : rv = NS_DispatchToMainThread(this);
114 155 : NS_ENSURE_SUCCESS(rv, rv);
115 :
116 155 : if (synchronize) {
117 0 : nsIThread *thread = NS_GetCurrentThread();
118 0 : while (mWaitingForRedirectCallback) {
119 0 : if (!NS_ProcessNextEvent(thread)) {
120 0 : return NS_ERROR_UNEXPECTED;
121 : }
122 : }
123 : }
124 :
125 155 : return NS_OK;
126 : }
127 :
128 : NS_IMETHODIMP
129 371 : nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback(nsresult result)
130 : {
131 371 : LOG(("nsAsyncRedirectVerifyHelper::OnRedirectVerifyCallback() "
132 : "result=%x expectedCBs=%u mResult=%x",
133 : result, mExpectedCallbacks, mResult));
134 :
135 371 : --mExpectedCallbacks;
136 :
137 : // If response indicates failure we may call back immediately
138 371 : if (NS_FAILED(result)) {
139 : // We chose to store the first failure-value (as opposed to the last)
140 15 : if (NS_SUCCEEDED(mResult))
141 15 : mResult = result;
142 :
143 : // If InitCallback() has been called, just invoke the callback and
144 : // return. Otherwise it will be invoked from InitCallback()
145 15 : if (mCallbackInitiated) {
146 0 : ExplicitCallback(mResult);
147 0 : return NS_OK;
148 : }
149 : }
150 :
151 : // If the expected-counter is in balance and InitCallback() was called, all
152 : // sinks have agreed that the redirect is ok and we can invoke our callback
153 371 : if (mCallbackInitiated && mExpectedCallbacks == 0) {
154 0 : ExplicitCallback(mResult);
155 : }
156 :
157 371 : return NS_OK;
158 : }
159 :
160 : nsresult
161 371 : nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect(nsIChannelEventSink *sink,
162 : nsIChannel *oldChannel,
163 : nsIChannel *newChannel,
164 : PRUint32 flags)
165 : {
166 371 : LOG(("nsAsyncRedirectVerifyHelper::DelegateOnChannelRedirect() "
167 : "sink=%p expectedCBs=%u mResult=%x",
168 : sink, mExpectedCallbacks, mResult));
169 :
170 371 : ++mExpectedCallbacks;
171 :
172 371 : if (IsOldChannelCanceled()) {
173 0 : LOG((" old channel has been canceled, cancel the redirect by "
174 : "emulating OnRedirectVerifyCallback..."));
175 0 : (void) OnRedirectVerifyCallback(NS_BINDING_ABORTED);
176 0 : return NS_BINDING_ABORTED;
177 : }
178 :
179 : nsresult rv =
180 371 : sink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, this);
181 :
182 371 : LOG((" result=%x expectedCBs=%u", rv, mExpectedCallbacks));
183 :
184 : // If the sink returns failure from this call the redirect is vetoed. We
185 : // emulate a callback from the sink in this case in order to perform all
186 : // the necessary logic.
187 371 : if (NS_FAILED(rv)) {
188 15 : LOG((" emulating OnRedirectVerifyCallback..."));
189 15 : (void) OnRedirectVerifyCallback(rv);
190 : }
191 :
192 371 : return rv; // Return the actual status since our caller may need it
193 : }
194 :
195 : void
196 155 : nsAsyncRedirectVerifyHelper::ExplicitCallback(nsresult result)
197 : {
198 155 : LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
199 : "result=%x expectedCBs=%u mCallbackInitiated=%u mResult=%x",
200 : result, mExpectedCallbacks, mCallbackInitiated, mResult));
201 :
202 : nsCOMPtr<nsIAsyncVerifyRedirectCallback>
203 310 : callback(do_QueryInterface(mOldChan));
204 :
205 155 : if (!callback || !mCallbackThread) {
206 0 : LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
207 : "callback=%p mCallbackThread=%p", callback.get(), mCallbackThread.get()));
208 : return;
209 : }
210 :
211 155 : mCallbackInitiated = false; // reset to ensure only one callback
212 155 : mWaitingForRedirectCallback = false;
213 :
214 : // Now, dispatch the callback on the event-target which called Init()
215 : nsRefPtr<nsIRunnable> event =
216 465 : new nsAsyncVerifyRedirectCallbackEvent(callback, result);
217 155 : if (!event) {
218 : NS_WARNING("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
219 0 : "failed creating callback event!");
220 : return;
221 : }
222 155 : nsresult rv = mCallbackThread->Dispatch(event, NS_DISPATCH_NORMAL);
223 155 : if (NS_FAILED(rv)) {
224 : NS_WARNING("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
225 0 : "failed dispatching callback event!");
226 : } else {
227 155 : LOG(("nsAsyncRedirectVerifyHelper::ExplicitCallback() "
228 : "dispatched callback event=%p", event.get()));
229 : }
230 :
231 : }
232 :
233 : void
234 152 : nsAsyncRedirectVerifyHelper::InitCallback()
235 : {
236 152 : LOG(("nsAsyncRedirectVerifyHelper::InitCallback() "
237 : "expectedCBs=%d mResult=%x", mExpectedCallbacks, mResult));
238 :
239 152 : mCallbackInitiated = true;
240 :
241 : // Invoke the callback if we are done
242 152 : if (mExpectedCallbacks == 0)
243 152 : ExplicitCallback(mResult);
244 152 : }
245 :
246 : NS_IMETHODIMP
247 155 : nsAsyncRedirectVerifyHelper::Run()
248 : {
249 : /* If the channel got canceled after it fired AsyncOnChannelRedirect
250 : * and before we got here, mostly because docloader load has been canceled,
251 : * we must completely ignore this notification and prevent any further
252 : * notification.
253 : */
254 155 : if (IsOldChannelCanceled()) {
255 0 : ExplicitCallback(NS_BINDING_ABORTED);
256 0 : return NS_OK;
257 : }
258 :
259 : // First, the global observer
260 155 : NS_ASSERTION(gIOService, "Must have an IO service at this point");
261 155 : LOG(("nsAsyncRedirectVerifyHelper::Run() calling gIOService..."));
262 : nsresult rv = gIOService->AsyncOnChannelRedirect(mOldChan, mNewChan,
263 155 : mFlags, this);
264 155 : if (NS_FAILED(rv)) {
265 3 : ExplicitCallback(rv);
266 3 : return NS_OK;
267 : }
268 :
269 : // Now, the per-channel observers
270 304 : nsCOMPtr<nsIChannelEventSink> sink;
271 152 : NS_QueryNotificationCallbacks(mOldChan, sink);
272 152 : if (sink) {
273 62 : LOG(("nsAsyncRedirectVerifyHelper::Run() calling sink..."));
274 62 : rv = DelegateOnChannelRedirect(sink, mOldChan, mNewChan, mFlags);
275 : }
276 :
277 : // All invocations to AsyncOnChannelRedirect has been done - call
278 : // InitCallback() to flag this
279 152 : InitCallback();
280 152 : return NS_OK;
281 : }
282 :
283 : bool
284 526 : nsAsyncRedirectVerifyHelper::IsOldChannelCanceled()
285 : {
286 : bool canceled;
287 : nsCOMPtr<nsIHttpChannelInternal> oldChannelInternal =
288 1052 : do_QueryInterface(mOldChan);
289 526 : if (oldChannelInternal) {
290 526 : oldChannelInternal->GetCanceled(&canceled);
291 526 : if (canceled)
292 0 : return true;
293 : }
294 :
295 526 : return false;
296 4392 : }
|