LCOV - code coverage report
Current view: directory - netwerk/base/src - nsPACMan.cpp (source / functions) Found Hit Coverage
Test: app.info Lines: 219 160 73.1 %
Date: 2012-06-02 Functions: 26 25 96.2 %

       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                 : }

Generated by: LCOV version 1.7