1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is mozilla.org code.
17 : *
18 : * The Initial Developer of the Original Code is Google Inc.
19 : * Portions created by the Initial Developer are Copyright (C) 2005
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Darin Fisher <darin@meer.net>
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "nsPACMan.h"
40 : #include "nsThreadUtils.h"
41 : #include "nsIDNSService.h"
42 : #include "nsIDNSListener.h"
43 : #include "nsICancelable.h"
44 : #include "nsIAuthPrompt.h"
45 : #include "nsIPromptFactory.h"
46 : #include "nsIHttpChannel.h"
47 : #include "nsIPrefService.h"
48 : #include "nsIPrefBranch.h"
49 : #include "nsNetUtil.h"
50 : #include "nsAutoPtr.h"
51 : #include "nsCRT.h"
52 : #include "prmon.h"
53 : #include "nsIAsyncVerifyRedirectCallback.h"
54 :
55 : //-----------------------------------------------------------------------------
56 :
57 : // Check to see if the underlying request was not an error page in the case of
58 : // a HTTP request. For other types of channels, just return true.
59 : static bool
60 10 : HttpRequestSucceeded(nsIStreamLoader *loader)
61 : {
62 20 : nsCOMPtr<nsIRequest> request;
63 10 : loader->GetRequest(getter_AddRefs(request));
64 :
65 10 : bool result = true; // default to assuming success
66 :
67 20 : nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
68 10 : if (httpChannel)
69 4 : httpChannel->GetRequestSucceeded(&result);
70 :
71 10 : return result;
72 : }
73 :
74 : //-----------------------------------------------------------------------------
75 :
76 : // These objects are stored in nsPACMan::mPendingQ
77 :
78 : class PendingPACQuery : public PRCList, public nsIDNSListener
79 11 : {
80 : public:
81 : NS_DECL_ISUPPORTS
82 : NS_DECL_NSIDNSLISTENER
83 :
84 11 : PendingPACQuery(nsPACMan *pacMan, nsIURI *uri, nsPACManCallback *callback)
85 : : mPACMan(pacMan)
86 : , mURI(uri)
87 11 : , mCallback(callback)
88 : {
89 11 : PR_INIT_CLIST(this);
90 11 : }
91 :
92 : nsresult Start(PRUint32 flags);
93 : void Complete(nsresult status, const nsCString &pacString);
94 :
95 : private:
96 : nsPACMan *mPACMan; // weak reference
97 : nsCOMPtr<nsIURI> mURI;
98 : nsRefPtr<nsPACManCallback> mCallback;
99 : nsCOMPtr<nsICancelable> mDNSRequest;
100 : };
101 :
102 : // This is threadsafe because we implement nsIDNSListener
103 143 : NS_IMPL_THREADSAFE_ISUPPORTS1(PendingPACQuery, nsIDNSListener)
104 :
105 : nsresult
106 11 : PendingPACQuery::Start(PRUint32 flags)
107 : {
108 11 : if (mDNSRequest)
109 0 : return NS_OK; // already started
110 :
111 : nsresult rv;
112 22 : nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
113 11 : if (NS_FAILED(rv)) {
114 0 : NS_WARNING("unable to get the DNS service");
115 0 : return rv;
116 : }
117 :
118 22 : nsCAutoString host;
119 11 : rv = mURI->GetAsciiHost(host);
120 11 : if (NS_FAILED(rv))
121 0 : return rv;
122 :
123 22 : rv = dns->AsyncResolve(host, flags, this, NS_GetCurrentThread(),
124 22 : getter_AddRefs(mDNSRequest));
125 11 : if (NS_FAILED(rv))
126 0 : NS_WARNING("DNS AsyncResolve failed");
127 :
128 11 : return rv;
129 : }
130 :
131 : // This may be called before or after OnLookupComplete
132 : void
133 11 : PendingPACQuery::Complete(nsresult status, const nsCString &pacString)
134 : {
135 11 : if (!mCallback)
136 0 : return;
137 :
138 11 : mCallback->OnQueryComplete(status, pacString);
139 11 : mCallback = nsnull;
140 :
141 11 : if (mDNSRequest) {
142 0 : mDNSRequest->Cancel(NS_ERROR_ABORT);
143 0 : mDNSRequest = nsnull;
144 : }
145 : }
146 :
147 : NS_IMETHODIMP
148 11 : PendingPACQuery::OnLookupComplete(nsICancelable *request,
149 : nsIDNSRecord *record,
150 : nsresult status)
151 : {
152 : // NOTE: we don't care about the results of this DNS query. We issued
153 : // this DNS query just to pre-populate our DNS cache.
154 :
155 11 : mDNSRequest = nsnull; // break reference cycle
156 :
157 : // If we've already completed this query then do nothing.
158 11 : if (!mCallback)
159 0 : return NS_OK;
160 :
161 : // We're no longer pending, so we can remove ourselves.
162 11 : PR_REMOVE_LINK(this);
163 :
164 22 : nsCAutoString pacString;
165 11 : status = mPACMan->GetProxyForURI(mURI, pacString);
166 11 : Complete(status, pacString);
167 :
168 11 : NS_RELEASE_THIS();
169 11 : return NS_OK;
170 : }
171 :
172 : //-----------------------------------------------------------------------------
173 :
174 10 : nsPACMan::nsPACMan()
175 : : mLoadPending(false)
176 : , mShutdown(false)
177 : , mScheduledReload(LL_MAXINT)
178 10 : , mLoadFailureCount(0)
179 : {
180 10 : PR_INIT_CLIST(&mPendingQ);
181 10 : }
182 :
183 20 : nsPACMan::~nsPACMan()
184 : {
185 10 : NS_ASSERTION(mLoader == nsnull, "pac man not shutdown properly");
186 10 : NS_ASSERTION(mPAC == nsnull, "pac man not shutdown properly");
187 10 : NS_ASSERTION(PR_CLIST_IS_EMPTY(&mPendingQ), "pac man not shutdown properly");
188 10 : }
189 :
190 : void
191 10 : nsPACMan::Shutdown()
192 : {
193 10 : CancelExistingLoad();
194 10 : ProcessPendingQ(NS_ERROR_ABORT);
195 :
196 10 : mPAC = nsnull;
197 10 : mShutdown = true;
198 10 : }
199 :
200 : nsresult
201 106 : nsPACMan::GetProxyForURI(nsIURI *uri, nsACString &result)
202 : {
203 106 : NS_ENSURE_STATE(!mShutdown);
204 :
205 106 : if (IsPACURI(uri)) {
206 0 : result.Truncate();
207 0 : return NS_OK;
208 : }
209 :
210 106 : MaybeReloadPAC();
211 :
212 106 : if (IsLoading())
213 11 : return NS_ERROR_IN_PROGRESS;
214 95 : if (!mPAC)
215 0 : return NS_ERROR_NOT_AVAILABLE;
216 :
217 190 : nsCAutoString spec, host;
218 95 : uri->GetAsciiSpec(spec);
219 95 : uri->GetAsciiHost(host);
220 :
221 95 : return mPAC->GetProxyForURI(spec, host, result);
222 : }
223 :
224 : nsresult
225 11 : nsPACMan::AsyncGetProxyForURI(nsIURI *uri, nsPACManCallback *callback)
226 : {
227 11 : NS_ENSURE_STATE(!mShutdown);
228 :
229 11 : MaybeReloadPAC();
230 :
231 11 : PendingPACQuery *query = new PendingPACQuery(this, uri, callback);
232 11 : if (!query)
233 0 : return NS_ERROR_OUT_OF_MEMORY;
234 11 : NS_ADDREF(query);
235 11 : PR_APPEND_LINK(query, &mPendingQ);
236 :
237 : // If we're waiting for the PAC file to load, then delay starting the query.
238 : // See OnStreamComplete. However, if this is the PAC URI then query right
239 : // away since we know the result will be DIRECT. We could shortcut some code
240 : // in this case by issuing the callback directly from here, but that would
241 : // require extra code, so we just go through the usual async code path.
242 11 : int isPACURI = IsPACURI(uri);
243 :
244 11 : if (IsLoading() && !isPACURI)
245 10 : return NS_OK;
246 :
247 1 : nsresult rv = query->Start(isPACURI ? 0 : nsIDNSService::RESOLVE_SPECULATE);
248 1 : if (rv == NS_ERROR_DNS_LOOKUP_QUEUE_FULL && !isPACURI) {
249 0 : query->OnLookupComplete(NULL, NULL, NS_OK);
250 0 : rv = NS_OK;
251 1 : } else if (NS_FAILED(rv)) {
252 0 : NS_WARNING("failed to start PAC query");
253 0 : PR_REMOVE_LINK(query);
254 0 : NS_RELEASE(query);
255 : }
256 :
257 1 : return rv;
258 : }
259 :
260 : nsresult
261 10 : nsPACMan::LoadPACFromURI(nsIURI *pacURI)
262 : {
263 10 : NS_ENSURE_STATE(!mShutdown);
264 10 : NS_ENSURE_ARG(pacURI || mPACURI);
265 :
266 : nsCOMPtr<nsIStreamLoader> loader =
267 20 : do_CreateInstance(NS_STREAMLOADER_CONTRACTID);
268 10 : NS_ENSURE_STATE(loader);
269 :
270 : // Since we might get called from nsProtocolProxyService::Init, we need to
271 : // post an event back to the main thread before we try to use the IO service.
272 : //
273 : // But, we need to flag ourselves as loading, so that we queue up any PAC
274 : // queries the enter between now and when we actually load the PAC file.
275 :
276 10 : if (!mLoadPending) {
277 : nsCOMPtr<nsIRunnable> event =
278 20 : NS_NewRunnableMethod(this, &nsPACMan::StartLoading);
279 : nsresult rv;
280 10 : if (NS_FAILED(rv = NS_DispatchToCurrentThread(event)))
281 0 : return rv;
282 20 : mLoadPending = true;
283 : }
284 :
285 10 : CancelExistingLoad();
286 :
287 10 : mLoader = loader;
288 10 : if (pacURI) {
289 10 : mPACURI = pacURI;
290 10 : mLoadFailureCount = 0; // reset
291 : }
292 10 : mScheduledReload = LL_MAXINT;
293 10 : mPAC = nsnull;
294 10 : return NS_OK;
295 : }
296 :
297 : void
298 10 : nsPACMan::StartLoading()
299 : {
300 10 : mLoadPending = false;
301 :
302 : // CancelExistingLoad was called...
303 10 : if (!mLoader) {
304 0 : ProcessPendingQ(NS_ERROR_ABORT);
305 0 : return;
306 : }
307 :
308 10 : if (NS_SUCCEEDED(mLoader->Init(this))) {
309 : // Always hit the origin server when loading PAC.
310 20 : nsCOMPtr<nsIIOService> ios = do_GetIOService();
311 10 : if (ios) {
312 20 : nsCOMPtr<nsIChannel> channel;
313 :
314 : // NOTE: This results in GetProxyForURI being called
315 10 : ios->NewChannelFromURI(mPACURI, getter_AddRefs(channel));
316 :
317 10 : if (channel) {
318 10 : channel->SetLoadFlags(nsIRequest::LOAD_BYPASS_CACHE);
319 10 : channel->SetNotificationCallbacks(this);
320 10 : if (NS_SUCCEEDED(channel->AsyncOpen(mLoader, nsnull)))
321 : return;
322 : }
323 : }
324 : }
325 :
326 0 : CancelExistingLoad();
327 0 : ProcessPendingQ(NS_ERROR_UNEXPECTED);
328 : }
329 :
330 : void
331 117 : nsPACMan::MaybeReloadPAC()
332 : {
333 117 : if (!mPACURI)
334 0 : return;
335 :
336 117 : if (PR_Now() > mScheduledReload)
337 0 : LoadPACFromURI(nsnull);
338 : }
339 :
340 : void
341 0 : nsPACMan::OnLoadFailure()
342 : {
343 0 : PRInt32 minInterval = 5; // 5 seconds
344 0 : PRInt32 maxInterval = 300; // 5 minutes
345 :
346 0 : nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
347 0 : if (prefs) {
348 0 : prefs->GetIntPref("network.proxy.autoconfig_retry_interval_min",
349 0 : &minInterval);
350 0 : prefs->GetIntPref("network.proxy.autoconfig_retry_interval_max",
351 0 : &maxInterval);
352 : }
353 :
354 0 : PRInt32 interval = minInterval << mLoadFailureCount++; // seconds
355 0 : if (!interval || interval > maxInterval)
356 0 : interval = maxInterval;
357 :
358 : #ifdef DEBUG
359 0 : printf("PAC load failure: will retry in %d seconds\n", interval);
360 : #endif
361 :
362 0 : mScheduledReload = PR_Now() + PRInt64(interval) * PR_USEC_PER_SEC;
363 0 : }
364 :
365 : void
366 20 : nsPACMan::CancelExistingLoad()
367 : {
368 20 : if (mLoader) {
369 0 : nsCOMPtr<nsIRequest> request;
370 0 : mLoader->GetRequest(getter_AddRefs(request));
371 0 : if (request)
372 0 : request->Cancel(NS_ERROR_ABORT);
373 0 : mLoader = nsnull;
374 : }
375 20 : }
376 :
377 : void
378 20 : nsPACMan::ProcessPendingQ(nsresult status)
379 : {
380 : // Now, start any pending queries
381 20 : PRCList *node = PR_LIST_HEAD(&mPendingQ);
382 50 : while (node != &mPendingQ) {
383 10 : PendingPACQuery *query = static_cast<PendingPACQuery *>(node);
384 10 : node = PR_NEXT_LINK(node);
385 10 : if (NS_SUCCEEDED(status)) {
386 : // keep the query in the list (so we can complete it from Shutdown if
387 : // necessary).
388 10 : status = query->Start(nsIDNSService::RESOLVE_SPECULATE);
389 : }
390 10 : if (status == NS_ERROR_DNS_LOOKUP_QUEUE_FULL) {
391 0 : query->OnLookupComplete(NULL, NULL, NS_OK);
392 0 : status = NS_OK;
393 10 : } else if (NS_FAILED(status)) {
394 : // remove the query from the list
395 0 : PR_REMOVE_LINK(query);
396 0 : query->Complete(status, EmptyCString());
397 0 : NS_RELEASE(query);
398 : }
399 : }
400 20 : }
401 :
402 215 : NS_IMPL_ISUPPORTS3(nsPACMan, nsIStreamLoaderObserver, nsIInterfaceRequestor,
403 : nsIChannelEventSink)
404 :
405 : NS_IMETHODIMP
406 10 : nsPACMan::OnStreamComplete(nsIStreamLoader *loader,
407 : nsISupports *context,
408 : nsresult status,
409 : PRUint32 dataLen,
410 : const PRUint8 *data)
411 : {
412 10 : if (mLoader != loader) {
413 : // If this happens, then it means that LoadPACFromURI was called more
414 : // than once before the initial call completed. In this case, status
415 : // should be NS_ERROR_ABORT, and if so, then we know that we can and
416 : // should delay any processing.
417 0 : if (status == NS_ERROR_ABORT)
418 0 : return NS_OK;
419 : }
420 :
421 10 : mLoader = nsnull;
422 :
423 10 : if (NS_SUCCEEDED(status) && HttpRequestSucceeded(loader)) {
424 : // Get the URI spec used to load this PAC script.
425 20 : nsCAutoString pacURI;
426 : {
427 20 : nsCOMPtr<nsIRequest> request;
428 10 : loader->GetRequest(getter_AddRefs(request));
429 20 : nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
430 10 : if (channel) {
431 20 : nsCOMPtr<nsIURI> uri;
432 10 : channel->GetURI(getter_AddRefs(uri));
433 10 : if (uri)
434 10 : uri->GetAsciiSpec(pacURI);
435 : }
436 : }
437 :
438 10 : if (!mPAC) {
439 10 : mPAC = do_CreateInstance(NS_PROXYAUTOCONFIG_CONTRACTID, &status);
440 10 : if (!mPAC)
441 0 : NS_WARNING("failed to instantiate PAC component");
442 : }
443 10 : if (NS_SUCCEEDED(status)) {
444 : // We assume that the PAC text is ASCII (or ISO-Latin-1). We've had this
445 : // assumption forever, and some real-world PAC scripts actually have some
446 : // non-ASCII text in comment blocks (see bug 296163).
447 10 : const char *text = (const char *) data;
448 10 : status = mPAC->Init(pacURI, NS_ConvertASCIItoUTF16(text, dataLen));
449 : }
450 :
451 : // Even if the PAC file could not be parsed, we did succeed in loading the
452 : // data for it.
453 10 : mLoadFailureCount = 0;
454 : } else {
455 : // We were unable to load the PAC file (presumably because of a network
456 : // failure). Try again a little later.
457 0 : OnLoadFailure();
458 : }
459 :
460 : // Reset mPAC if necessary
461 10 : if (mPAC && NS_FAILED(status))
462 0 : mPAC = nsnull;
463 :
464 10 : ProcessPendingQ(status);
465 10 : return NS_OK;
466 : }
467 :
468 : NS_IMETHODIMP
469 27 : nsPACMan::GetInterface(const nsIID &iid, void **result)
470 : {
471 : // In case loading the PAC file requires authentication.
472 27 : if (iid.Equals(NS_GET_IID(nsIAuthPrompt))) {
473 0 : nsCOMPtr<nsIPromptFactory> promptFac = do_GetService("@mozilla.org/prompter;1");
474 0 : NS_ENSURE_TRUE(promptFac, NS_ERROR_FAILURE);
475 0 : return promptFac->GetPrompt(nsnull, iid, reinterpret_cast<void**>(result));
476 : }
477 :
478 : // In case loading the PAC file results in a redirect.
479 27 : if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
480 2 : NS_ADDREF_THIS();
481 2 : *result = static_cast<nsIChannelEventSink *>(this);
482 2 : return NS_OK;
483 : }
484 :
485 25 : return NS_ERROR_NO_INTERFACE;
486 : }
487 :
488 : NS_IMETHODIMP
489 2 : nsPACMan::AsyncOnChannelRedirect(nsIChannel *oldChannel, nsIChannel *newChannel,
490 : PRUint32 flags,
491 : nsIAsyncVerifyRedirectCallback *callback)
492 : {
493 2 : nsresult rv = NS_OK;
494 2 : if (NS_FAILED((rv = newChannel->GetURI(getter_AddRefs(mPACURI)))))
495 0 : return rv;
496 :
497 2 : callback->OnRedirectVerifyCallback(NS_OK);
498 2 : return NS_OK;
499 : }
|