Back to index

lightning-sunbird  0.9+nobinonly
nsPACMan.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* vim:set ts=2 sw=2 sts=2 et cindent: */
00003 /* ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is mozilla.org code.
00017  *
00018  * The Initial Developer of the Original Code is Google Inc.
00019  * Portions created by the Initial Developer are Copyright (C) 2005
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *  Darin Fisher <darin@meer.net>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "nsPACMan.h"
00040 #include "nsIDNSService.h"
00041 #include "nsIDNSListener.h"
00042 #include "nsICancelable.h"
00043 #include "nsIAuthPrompt.h"
00044 #include "nsIHttpChannel.h"
00045 #include "nsIPrefService.h"
00046 #include "nsIPrefBranch.h"
00047 #include "nsEventQueueUtils.h"
00048 #include "nsNetUtil.h"
00049 #include "nsAutoLock.h"
00050 #include "nsAutoPtr.h"
00051 #include "nsCRT.h"
00052 #include "prmon.h"
00053 
00054 //-----------------------------------------------------------------------------
00055 
00056 // Check to see if the underlying request was not an error page in the case of
00057 // a HTTP request.  For other types of channels, just return true.
00058 static PRBool
00059 HttpRequestSucceeded(nsIStreamLoader *loader)
00060 {
00061   nsCOMPtr<nsIRequest> request;
00062   loader->GetRequest(getter_AddRefs(request));
00063 
00064   PRBool result = PR_TRUE;  // default to assuming success
00065 
00066   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
00067   if (httpChannel)
00068     httpChannel->GetRequestSucceeded(&result);
00069 
00070   return result;
00071 }
00072 
00073 //-----------------------------------------------------------------------------
00074 
00075 // These objects are stored in nsPACMan::mPendingQ
00076 
00077 class PendingPACQuery : public PRCList, public nsIDNSListener
00078 {
00079 public:
00080   NS_DECL_ISUPPORTS
00081   NS_DECL_NSIDNSLISTENER
00082 
00083   PendingPACQuery(nsPACMan *pacMan, nsIURI *uri, nsPACManCallback *callback)
00084     : mPACMan(pacMan)
00085     , mURI(uri)
00086     , mCallback(callback)
00087   {
00088     PR_INIT_CLIST(this);
00089   }
00090 
00091   nsresult Start();
00092   void     Complete(nsresult status, const nsCString &pacString);
00093 
00094 private:
00095   nsPACMan                  *mPACMan;  // weak reference
00096   nsCOMPtr<nsIURI>           mURI;
00097   nsRefPtr<nsPACManCallback> mCallback;
00098   nsCOMPtr<nsICancelable>    mDNSRequest;
00099 };
00100 
00101 // This is threadsafe because we implement nsIDNSListener
00102 NS_IMPL_THREADSAFE_ISUPPORTS1(PendingPACQuery, nsIDNSListener)
00103 
00104 nsresult
00105 PendingPACQuery::Start()
00106 {
00107   if (mDNSRequest)
00108     return NS_OK;  // already started
00109 
00110   nsresult rv;
00111   nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
00112   if (NS_FAILED(rv)) {
00113     NS_WARNING("unable to get the DNS service");
00114     return rv;
00115   }
00116 
00117   nsCAutoString host;
00118   rv = mURI->GetAsciiHost(host);
00119   if (NS_FAILED(rv))
00120     return rv;
00121 
00122   nsCOMPtr<nsIEventQueue> eventQ;
00123   rv = NS_GetCurrentEventQ(getter_AddRefs(eventQ));
00124   if (NS_FAILED(rv))
00125     return rv;
00126 
00127   rv = dns->AsyncResolve(host, 0, this, eventQ, getter_AddRefs(mDNSRequest));
00128   if (NS_FAILED(rv))
00129     NS_WARNING("DNS AsyncResolve failed");
00130 
00131   return rv;
00132 }
00133 
00134 // This may be called before or after OnLookupComplete
00135 void
00136 PendingPACQuery::Complete(nsresult status, const nsCString &pacString)
00137 {
00138   if (!mCallback)
00139     return;
00140 
00141   mCallback->OnQueryComplete(status, pacString);
00142   mCallback = nsnull;
00143 
00144   if (mDNSRequest) {
00145     mDNSRequest->Cancel(NS_ERROR_ABORT);
00146     mDNSRequest = nsnull;
00147   }
00148 }
00149 
00150 NS_IMETHODIMP
00151 PendingPACQuery::OnLookupComplete(nsICancelable *request,
00152                                   nsIDNSRecord *record,
00153                                   nsresult status)
00154 {
00155   // NOTE: we don't care about the results of this DNS query.  We issued
00156   //       this DNS query just to pre-populate our DNS cache.
00157  
00158   mDNSRequest = nsnull;  // break reference cycle
00159 
00160   // If we've already completed this query then do nothing.
00161   if (!mCallback)
00162     return NS_OK;
00163 
00164   // We're no longer pending, so we can remove ourselves.
00165   PR_REMOVE_LINK(this);
00166   NS_RELEASE_THIS();
00167 
00168   nsCAutoString pacString;
00169   status = mPACMan->GetProxyForURI(mURI, pacString);
00170   Complete(status, pacString);
00171   return NS_OK;
00172 }
00173 
00174 //-----------------------------------------------------------------------------
00175 
00176 nsPACMan::nsPACMan()
00177   : mLoadEvent(nsnull)
00178   , mShutdown(PR_FALSE)
00179   , mScheduledReload(LL_MAXINT)
00180   , mLoadFailureCount(0)
00181 {
00182   PR_INIT_CLIST(&mPendingQ);
00183 }
00184 
00185 nsPACMan::~nsPACMan()
00186 {
00187   NS_ASSERTION(mLoader == nsnull, "pac man not shutdown properly");
00188   NS_ASSERTION(mPAC == nsnull, "pac man not shutdown properly");
00189   NS_ASSERTION(PR_CLIST_IS_EMPTY(&mPendingQ), "pac man not shutdown properly");
00190 }
00191 
00192 void
00193 nsPACMan::Shutdown()
00194 {
00195   CancelExistingLoad();
00196   ProcessPendingQ(NS_ERROR_ABORT);
00197 
00198   mPAC = nsnull;
00199   mShutdown = PR_TRUE;
00200 }
00201 
00202 nsresult
00203 nsPACMan::GetProxyForURI(nsIURI *uri, nsACString &result)
00204 {
00205   NS_ENSURE_STATE(!mShutdown);
00206 
00207   if (IsPACURI(uri)) {
00208     result.Truncate();
00209     return NS_OK;
00210   }
00211 
00212   MaybeReloadPAC();
00213 
00214   if (IsLoading())
00215     return NS_ERROR_IN_PROGRESS;
00216   if (!mPAC)
00217     return NS_ERROR_NOT_AVAILABLE;
00218 
00219   nsCAutoString spec, host;
00220   uri->GetAsciiSpec(spec);
00221   uri->GetAsciiHost(host);
00222 
00223   return mPAC->GetProxyForURI(spec, host, result);
00224 }
00225 
00226 nsresult
00227 nsPACMan::AsyncGetProxyForURI(nsIURI *uri, nsPACManCallback *callback)
00228 {
00229   NS_ENSURE_STATE(!mShutdown);
00230 
00231   MaybeReloadPAC();
00232 
00233   PendingPACQuery *query = new PendingPACQuery(this, uri, callback);
00234   if (!query)
00235     return NS_ERROR_OUT_OF_MEMORY;
00236   NS_ADDREF(query);
00237   PR_APPEND_LINK(query, &mPendingQ);
00238 
00239   // If we're waiting for the PAC file to load, then delay starting the query.
00240   // See OnStreamComplete.  However, if this is the PAC URI then query right
00241   // away since we know the result will be DIRECT.  We could shortcut some code
00242   // in this case by issuing the callback directly from here, but that would
00243   // require extra code, so we just go through the usual async code path.
00244   if (IsLoading() && !IsPACURI(uri))
00245     return NS_OK;
00246 
00247   nsresult rv = query->Start();
00248   if (NS_FAILED(rv)) {
00249     NS_WARNING("failed to start PAC query");
00250     PR_REMOVE_LINK(query);
00251     NS_RELEASE(query);
00252   }
00253 
00254   return rv;
00255 }
00256 
00257 void *PR_CALLBACK
00258 nsPACMan::LoadEvent_Handle(PLEvent *ev)
00259 {
00260   NS_REINTERPRET_CAST(nsPACMan *, PL_GetEventOwner(ev))->StartLoading();
00261   return nsnull;
00262 }
00263 
00264 void PR_CALLBACK
00265 nsPACMan::LoadEvent_Destroy(PLEvent *ev)
00266 {
00267   nsPACMan *self = NS_REINTERPRET_CAST(nsPACMan *, PL_GetEventOwner(ev));
00268   self->mLoadEvent = nsnull;
00269   self->Release();
00270   delete ev;
00271 }
00272 
00273 nsresult
00274 nsPACMan::LoadPACFromURI(nsIURI *pacURI)
00275 {
00276   NS_ENSURE_STATE(!mShutdown);
00277 
00278   nsCOMPtr<nsIStreamLoader> loader =
00279       do_CreateInstance(NS_STREAMLOADER_CONTRACTID);
00280   NS_ENSURE_STATE(loader);
00281 
00282   // Since we might get called from nsProtocolProxyService::Init, we need to
00283   // post an event back to the main thread before we try to use the IO service.
00284   //
00285   // But, we need to flag ourselves as loading, so that we queue up any PAC
00286   // queries the enter between now and when we actually load the PAC file.
00287 
00288   if (!mLoadEvent) {
00289     mLoadEvent = new PLEvent;
00290     if (!mLoadEvent)
00291       return NS_ERROR_OUT_OF_MEMORY;
00292 
00293     NS_ADDREF_THIS();
00294     PL_InitEvent(mLoadEvent, this, LoadEvent_Handle, LoadEvent_Destroy);
00295 
00296     nsCOMPtr<nsIEventQueue> eventQ;
00297     nsresult rv = NS_GetCurrentEventQ(getter_AddRefs(eventQ));
00298     if (NS_FAILED(rv) || NS_FAILED(rv = eventQ->PostEvent(mLoadEvent))) {
00299       PL_DestroyEvent(mLoadEvent);
00300       return rv;
00301     }
00302   }
00303 
00304   CancelExistingLoad();
00305 
00306   mLoader = loader;
00307   mPACURI = pacURI;
00308   mPAC = nsnull;
00309   return NS_OK;
00310 }
00311 
00312 nsresult
00313 nsPACMan::StartLoading()
00314 {
00315   // CancelExistingLoad was called...
00316   if (!mLoader) {
00317     ProcessPendingQ(NS_ERROR_ABORT);
00318     return NS_OK;
00319   }
00320 
00321   // Always hit the origin server when loading PAC.
00322   nsCOMPtr<nsIIOService> ios = do_GetIOService();
00323   if (ios) {
00324     nsCOMPtr<nsIChannel> channel;
00325 
00326     // NOTE: This results in GetProxyForURI being called
00327     ios->NewChannelFromURI(mPACURI, getter_AddRefs(channel));
00328 
00329     if (channel) {
00330       channel->SetLoadFlags(nsIRequest::LOAD_BYPASS_CACHE);
00331       channel->SetNotificationCallbacks(this);
00332       if (NS_SUCCEEDED(mLoader->Init(channel, this, nsnull)))
00333         return NS_OK;
00334     }
00335   }
00336 
00337   CancelExistingLoad();
00338   ProcessPendingQ(NS_ERROR_UNEXPECTED);
00339   return NS_OK;
00340 }
00341 
00342 void
00343 nsPACMan::MaybeReloadPAC()
00344 {
00345   if (!mPACURI)
00346     return;
00347 
00348   if (PR_Now() > mScheduledReload) {
00349     mScheduledReload = LL_MAXINT;
00350     LoadPACFromURI(mPACURI);
00351   }
00352 }
00353 
00354 void
00355 nsPACMan::OnLoadFailure()
00356 {
00357   PRInt32 minInterval = 5;    // 5 seconds
00358   PRInt32 maxInterval = 300;  // 5 minutes
00359 
00360   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
00361   if (prefs) {
00362     prefs->GetIntPref("network.proxy.autoconfig_retry_interval_min",
00363                       &minInterval);
00364     prefs->GetIntPref("network.proxy.autoconfig_retry_interval_max",
00365                       &maxInterval);
00366   }
00367 
00368   PRInt32 interval = minInterval << mLoadFailureCount++;  // seconds
00369   if (!interval || interval > maxInterval)
00370     interval = maxInterval;
00371 
00372 #ifdef DEBUG
00373   printf("PAC load failure: will retry in %d seconds\n", interval);
00374 #endif
00375 
00376   mScheduledReload = PR_Now() + PRInt64(interval) * PR_USEC_PER_SEC;
00377 }
00378 
00379 void
00380 nsPACMan::CancelExistingLoad()
00381 {
00382   if (mLoader) {
00383     nsCOMPtr<nsIRequest> request;
00384     mLoader->GetRequest(getter_AddRefs(request));
00385     if (request)
00386       request->Cancel(NS_ERROR_ABORT);
00387     mLoader = nsnull;
00388   }
00389 }
00390 
00391 void
00392 nsPACMan::ProcessPendingQ(nsresult status)
00393 {
00394   // Now, start any pending queries
00395   PRCList *node = PR_LIST_HEAD(&mPendingQ);
00396   while (node != &mPendingQ) {
00397     PendingPACQuery *query = NS_STATIC_CAST(PendingPACQuery *, node);
00398     node = PR_NEXT_LINK(node);
00399     if (NS_SUCCEEDED(status)) {
00400       // keep the query in the list (so we can complete it from Shutdown if
00401       // necessary).
00402       status = query->Start();
00403     }
00404     if (NS_FAILED(status)) {
00405       // remove the query from the list
00406       PR_REMOVE_LINK(query);
00407       query->Complete(status, EmptyCString());
00408       NS_RELEASE(query);
00409     }
00410   }
00411 }
00412 
00413 NS_IMPL_ISUPPORTS3(nsPACMan, nsIStreamLoaderObserver, nsIInterfaceRequestor,
00414                    nsIChannelEventSink)
00415 
00416 NS_IMETHODIMP
00417 nsPACMan::OnStreamComplete(nsIStreamLoader *loader,
00418                            nsISupports *context,
00419                            nsresult status,
00420                            PRUint32 dataLen,
00421                            const PRUint8 *data)
00422 {
00423   if (mLoader != loader) {
00424     // If this happens, then it means that LoadPACFromURI was called more
00425     // than once before the initial call completed.  In this case, status
00426     // should be NS_ERROR_ABORT, and if so, then we know that we can and
00427     // should delay any processing.
00428     if (status == NS_ERROR_ABORT)
00429       return NS_OK;
00430   }
00431 
00432   mLoader = nsnull;
00433 
00434   if (NS_SUCCEEDED(status) && HttpRequestSucceeded(loader)) {
00435     // Get the URI spec used to load this PAC script.
00436     nsCAutoString pacURI;
00437     {
00438       nsCOMPtr<nsIRequest> request;
00439       loader->GetRequest(getter_AddRefs(request));
00440       nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
00441       if (channel) {
00442         nsCOMPtr<nsIURI> uri;
00443         channel->GetURI(getter_AddRefs(uri));
00444         if (uri)
00445           uri->GetAsciiSpec(pacURI);
00446       }
00447     }
00448 
00449     if (!mPAC) {
00450       mPAC = do_CreateInstance(NS_PROXYAUTOCONFIG_CONTRACTID, &status);
00451       if (!mPAC)
00452         NS_WARNING("failed to instantiate PAC component");
00453     }
00454     if (NS_SUCCEEDED(status)) {
00455       // We assume that the PAC text is ASCII (or ISO-Latin-1).  We've had this
00456       // assumption forever, and some real-world PAC scripts actually have some
00457       // non-ASCII text in comment blocks (see bug 296163).
00458       const char *text = (const char *) data;
00459       status = mPAC->Init(pacURI, NS_ConvertASCIItoUTF16(text, dataLen));
00460     }
00461 
00462     // Even if the PAC file could not be parsed, we did succeed in loading the
00463     // data for it.
00464     mLoadFailureCount = 0;
00465   } else {
00466     // We were unable to load the PAC file (presumably because of a network
00467     // failure).  Try again a little later.
00468     OnLoadFailure();
00469   }
00470 
00471   // Reset mPAC if necessary
00472   if (mPAC && NS_FAILED(status))
00473     mPAC = nsnull;
00474 
00475   ProcessPendingQ(status);
00476   return NS_OK;
00477 }
00478 
00479 NS_IMETHODIMP
00480 nsPACMan::GetInterface(const nsIID &iid, void **result)
00481 {
00482   // In case loading the PAC file requires authentication.
00483   if (iid.Equals(NS_GET_IID(nsIAuthPrompt)))
00484     return CallCreateInstance(NS_DEFAULTAUTHPROMPT_CONTRACTID,
00485                               nsnull, iid, result);
00486 
00487   // In case loading the PAC file results in a redirect.
00488   if (iid.Equals(NS_GET_IID(nsIChannelEventSink))) {
00489     NS_ADDREF_THIS();
00490     *result = NS_STATIC_CAST(nsIChannelEventSink *, this);
00491     return NS_OK;
00492   }
00493 
00494   return NS_ERROR_NO_INTERFACE;
00495 }
00496 
00497 NS_IMETHODIMP
00498 nsPACMan::OnChannelRedirect(nsIChannel *oldChannel, nsIChannel *newChannel,
00499                             PRUint32 flags)
00500 {
00501   return newChannel->GetURI(getter_AddRefs(mPACURI));
00502 }