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
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/
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
16 * The Original Code is mozilla.org code.
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.
23 * Darin Fisher <darin@meer.net>
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.
37 * ***** END LICENSE BLOCK ***** */
40 #include "nsThreadUtils.h"
41 #include "nsIDNSService.h"
42 #include "nsIDNSListener.h"
43 #include "nsICancelable.h"
44 #include "nsIAuthPrompt.h"
45 #include "nsIHttpChannel.h"
46 #include "nsIPrefService.h"
47 #include "nsIPrefBranch.h"
48 #include "nsNetUtil.h"
49 #include "nsAutoLock.h"
50 #include "nsAutoPtr.h"
54 //-----------------------------------------------------------------------------
56 // Check to see if the underlying request was not an error page in the case of
57 // a HTTP request. For other types of channels, just return true.
59 HttpRequestSucceeded(nsIStreamLoader
*loader
)
61 nsCOMPtr
<nsIRequest
> request
;
62 loader
->GetRequest(getter_AddRefs(request
));
64 PRBool result
= PR_TRUE
; // default to assuming success
66 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface(request
);
68 httpChannel
->GetRequestSucceeded(&result
);
73 //-----------------------------------------------------------------------------
75 // These objects are stored in nsPACMan::mPendingQ
77 class PendingPACQuery
: public PRCList
, public nsIDNSListener
81 NS_DECL_NSIDNSLISTENER
83 PendingPACQuery(nsPACMan
*pacMan
, nsIURI
*uri
, nsPACManCallback
*callback
)
92 void Complete(nsresult status
, const nsCString
&pacString
);
95 nsPACMan
*mPACMan
; // weak reference
96 nsCOMPtr
<nsIURI
> mURI
;
97 nsRefPtr
<nsPACManCallback
> mCallback
;
98 nsCOMPtr
<nsICancelable
> mDNSRequest
;
101 // This is threadsafe because we implement nsIDNSListener
102 NS_IMPL_THREADSAFE_ISUPPORTS1(PendingPACQuery
, nsIDNSListener
)
105 PendingPACQuery::Start()
108 return NS_OK
; // already started
111 nsCOMPtr
<nsIDNSService
> dns
= do_GetService(NS_DNSSERVICE_CONTRACTID
, &rv
);
113 NS_WARNING("unable to get the DNS service");
118 rv
= mURI
->GetAsciiHost(host
);
122 rv
= dns
->AsyncResolve(host
, 0, this, NS_GetCurrentThread(),
123 getter_AddRefs(mDNSRequest
));
125 NS_WARNING("DNS AsyncResolve failed");
130 // This may be called before or after OnLookupComplete
132 PendingPACQuery::Complete(nsresult status
, const nsCString
&pacString
)
137 mCallback
->OnQueryComplete(status
, pacString
);
141 mDNSRequest
->Cancel(NS_ERROR_ABORT
);
142 mDNSRequest
= nsnull
;
147 PendingPACQuery::OnLookupComplete(nsICancelable
*request
,
148 nsIDNSRecord
*record
,
151 // NOTE: we don't care about the results of this DNS query. We issued
152 // this DNS query just to pre-populate our DNS cache.
154 mDNSRequest
= nsnull
; // break reference cycle
156 // If we've already completed this query then do nothing.
160 // We're no longer pending, so we can remove ourselves.
161 PR_REMOVE_LINK(this);
164 nsCAutoString pacString
;
165 status
= mPACMan
->GetProxyForURI(mURI
, pacString
);
166 Complete(status
, pacString
);
170 //-----------------------------------------------------------------------------
173 : mLoadPending(PR_FALSE
)
174 , mShutdown(PR_FALSE
)
175 , mScheduledReload(LL_MAXINT
)
176 , mLoadFailureCount(0)
178 PR_INIT_CLIST(&mPendingQ
);
181 nsPACMan::~nsPACMan()
183 NS_ASSERTION(mLoader
== nsnull
, "pac man not shutdown properly");
184 NS_ASSERTION(mPAC
== nsnull
, "pac man not shutdown properly");
185 NS_ASSERTION(PR_CLIST_IS_EMPTY(&mPendingQ
), "pac man not shutdown properly");
191 CancelExistingLoad();
192 ProcessPendingQ(NS_ERROR_ABORT
);
199 nsPACMan::GetProxyForURI(nsIURI
*uri
, nsACString
&result
)
201 NS_ENSURE_STATE(!mShutdown
);
211 return NS_ERROR_IN_PROGRESS
;
213 return NS_ERROR_NOT_AVAILABLE
;
215 nsCAutoString spec
, host
;
216 uri
->GetAsciiSpec(spec
);
217 uri
->GetAsciiHost(host
);
219 return mPAC
->GetProxyForURI(spec
, host
, result
);
223 nsPACMan::AsyncGetProxyForURI(nsIURI
*uri
, nsPACManCallback
*callback
)
225 NS_ENSURE_STATE(!mShutdown
);
229 PendingPACQuery
*query
= new PendingPACQuery(this, uri
, callback
);
231 return NS_ERROR_OUT_OF_MEMORY
;
233 PR_APPEND_LINK(query
, &mPendingQ
);
235 // If we're waiting for the PAC file to load, then delay starting the query.
236 // See OnStreamComplete. However, if this is the PAC URI then query right
237 // away since we know the result will be DIRECT. We could shortcut some code
238 // in this case by issuing the callback directly from here, but that would
239 // require extra code, so we just go through the usual async code path.
240 if (IsLoading() && !IsPACURI(uri
))
243 nsresult rv
= query
->Start();
245 NS_WARNING("failed to start PAC query");
246 PR_REMOVE_LINK(query
);
254 nsPACMan::LoadPACFromURI(nsIURI
*pacURI
)
256 NS_ENSURE_STATE(!mShutdown
);
257 NS_ENSURE_ARG(pacURI
|| mPACURI
);
259 nsCOMPtr
<nsIStreamLoader
> loader
=
260 do_CreateInstance(NS_STREAMLOADER_CONTRACTID
);
261 NS_ENSURE_STATE(loader
);
263 // Since we might get called from nsProtocolProxyService::Init, we need to
264 // post an event back to the main thread before we try to use the IO service.
266 // But, we need to flag ourselves as loading, so that we queue up any PAC
267 // queries the enter between now and when we actually load the PAC file.
270 nsCOMPtr
<nsIRunnable
> event
=
271 NS_NEW_RUNNABLE_METHOD(nsPACMan
, this, StartLoading
);
273 if (NS_FAILED(rv
= NS_DispatchToCurrentThread(event
)))
275 mLoadPending
= PR_TRUE
;
278 CancelExistingLoad();
283 mLoadFailureCount
= 0; // reset
285 mScheduledReload
= LL_MAXINT
;
291 nsPACMan::StartLoading()
293 mLoadPending
= PR_FALSE
;
295 // CancelExistingLoad was called...
297 ProcessPendingQ(NS_ERROR_ABORT
);
301 if (NS_SUCCEEDED(mLoader
->Init(this))) {
302 // Always hit the origin server when loading PAC.
303 nsCOMPtr
<nsIIOService
> ios
= do_GetIOService();
305 nsCOMPtr
<nsIChannel
> channel
;
307 // NOTE: This results in GetProxyForURI being called
308 ios
->NewChannelFromURI(mPACURI
, getter_AddRefs(channel
));
311 channel
->SetLoadFlags(nsIRequest::LOAD_BYPASS_CACHE
);
312 channel
->SetNotificationCallbacks(this);
313 if (NS_SUCCEEDED(channel
->AsyncOpen(mLoader
, nsnull
)))
319 CancelExistingLoad();
320 ProcessPendingQ(NS_ERROR_UNEXPECTED
);
324 nsPACMan::MaybeReloadPAC()
329 if (PR_Now() > mScheduledReload
)
330 LoadPACFromURI(nsnull
);
334 nsPACMan::OnLoadFailure()
336 PRInt32 minInterval
= 5; // 5 seconds
337 PRInt32 maxInterval
= 300; // 5 minutes
339 nsCOMPtr
<nsIPrefBranch
> prefs
= do_GetService(NS_PREFSERVICE_CONTRACTID
);
341 prefs
->GetIntPref("network.proxy.autoconfig_retry_interval_min",
343 prefs
->GetIntPref("network.proxy.autoconfig_retry_interval_max",
347 PRInt32 interval
= minInterval
<< mLoadFailureCount
++; // seconds
348 if (!interval
|| interval
> maxInterval
)
349 interval
= maxInterval
;
352 printf("PAC load failure: will retry in %d seconds\n", interval
);
355 mScheduledReload
= PR_Now() + PRInt64(interval
) * PR_USEC_PER_SEC
;
359 nsPACMan::CancelExistingLoad()
362 nsCOMPtr
<nsIRequest
> request
;
363 mLoader
->GetRequest(getter_AddRefs(request
));
365 request
->Cancel(NS_ERROR_ABORT
);
371 nsPACMan::ProcessPendingQ(nsresult status
)
373 // Now, start any pending queries
374 PRCList
*node
= PR_LIST_HEAD(&mPendingQ
);
375 while (node
!= &mPendingQ
) {
376 PendingPACQuery
*query
= static_cast<PendingPACQuery
*>(node
);
377 node
= PR_NEXT_LINK(node
);
378 if (NS_SUCCEEDED(status
)) {
379 // keep the query in the list (so we can complete it from Shutdown if
381 status
= query
->Start();
383 if (NS_FAILED(status
)) {
384 // remove the query from the list
385 PR_REMOVE_LINK(query
);
386 query
->Complete(status
, EmptyCString());
392 NS_IMPL_ISUPPORTS3(nsPACMan
, nsIStreamLoaderObserver
, nsIInterfaceRequestor
,
396 nsPACMan::OnStreamComplete(nsIStreamLoader
*loader
,
397 nsISupports
*context
,
402 if (mLoader
!= loader
) {
403 // If this happens, then it means that LoadPACFromURI was called more
404 // than once before the initial call completed. In this case, status
405 // should be NS_ERROR_ABORT, and if so, then we know that we can and
406 // should delay any processing.
407 if (status
== NS_ERROR_ABORT
)
413 if (NS_SUCCEEDED(status
) && HttpRequestSucceeded(loader
)) {
414 // Get the URI spec used to load this PAC script.
415 nsCAutoString pacURI
;
417 nsCOMPtr
<nsIRequest
> request
;
418 loader
->GetRequest(getter_AddRefs(request
));
419 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(request
);
421 nsCOMPtr
<nsIURI
> uri
;
422 channel
->GetURI(getter_AddRefs(uri
));
424 uri
->GetAsciiSpec(pacURI
);
429 mPAC
= do_CreateInstance(NS_PROXYAUTOCONFIG_CONTRACTID
, &status
);
431 NS_WARNING("failed to instantiate PAC component");
433 if (NS_SUCCEEDED(status
)) {
434 // We assume that the PAC text is ASCII (or ISO-Latin-1). We've had this
435 // assumption forever, and some real-world PAC scripts actually have some
436 // non-ASCII text in comment blocks (see bug 296163).
437 const char *text
= (const char *) data
;
438 status
= mPAC
->Init(pacURI
, NS_ConvertASCIItoUTF16(text
, dataLen
));
441 // Even if the PAC file could not be parsed, we did succeed in loading the
443 mLoadFailureCount
= 0;
445 // We were unable to load the PAC file (presumably because of a network
446 // failure). Try again a little later.
450 // Reset mPAC if necessary
451 if (mPAC
&& NS_FAILED(status
))
454 ProcessPendingQ(status
);
459 nsPACMan::GetInterface(const nsIID
&iid
, void **result
)
461 // In case loading the PAC file requires authentication.
462 if (iid
.Equals(NS_GET_IID(nsIAuthPrompt
)))
463 return CallCreateInstance(NS_DEFAULTAUTHPROMPT_CONTRACTID
,
464 nsnull
, iid
, result
);
466 // In case loading the PAC file results in a redirect.
467 if (iid
.Equals(NS_GET_IID(nsIChannelEventSink
))) {
469 *result
= static_cast<nsIChannelEventSink
*>(this);
473 return NS_ERROR_NO_INTERFACE
;
477 nsPACMan::OnChannelRedirect(nsIChannel
*oldChannel
, nsIChannel
*newChannel
,
480 return newChannel
->GetURI(getter_AddRefs(mPACURI
));