Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / netwerk / base / src / nsPACMan.cpp
blobb3f3bb4e95247d445025ec7c044afb0ae63a79cd
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
14 * License.
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.
22 * Contributor(s):
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 ***** */
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 "nsIHttpChannel.h"
46 #include "nsIPrefService.h"
47 #include "nsIPrefBranch.h"
48 #include "nsNetUtil.h"
49 #include "nsAutoLock.h"
50 #include "nsAutoPtr.h"
51 #include "nsCRT.h"
52 #include "prmon.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.
58 static PRBool
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);
67 if (httpChannel)
68 httpChannel->GetRequestSucceeded(&result);
70 return result;
73 //-----------------------------------------------------------------------------
75 // These objects are stored in nsPACMan::mPendingQ
77 class PendingPACQuery : public PRCList, public nsIDNSListener
79 public:
80 NS_DECL_ISUPPORTS
81 NS_DECL_NSIDNSLISTENER
83 PendingPACQuery(nsPACMan *pacMan, nsIURI *uri, nsPACManCallback *callback)
84 : mPACMan(pacMan)
85 , mURI(uri)
86 , mCallback(callback)
88 PR_INIT_CLIST(this);
91 nsresult Start();
92 void Complete(nsresult status, const nsCString &pacString);
94 private:
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)
104 nsresult
105 PendingPACQuery::Start()
107 if (mDNSRequest)
108 return NS_OK; // already started
110 nsresult rv;
111 nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID, &rv);
112 if (NS_FAILED(rv)) {
113 NS_WARNING("unable to get the DNS service");
114 return rv;
117 nsCAutoString host;
118 rv = mURI->GetAsciiHost(host);
119 if (NS_FAILED(rv))
120 return rv;
122 rv = dns->AsyncResolve(host, 0, this, NS_GetCurrentThread(),
123 getter_AddRefs(mDNSRequest));
124 if (NS_FAILED(rv))
125 NS_WARNING("DNS AsyncResolve failed");
127 return rv;
130 // This may be called before or after OnLookupComplete
131 void
132 PendingPACQuery::Complete(nsresult status, const nsCString &pacString)
134 if (!mCallback)
135 return;
137 mCallback->OnQueryComplete(status, pacString);
138 mCallback = nsnull;
140 if (mDNSRequest) {
141 mDNSRequest->Cancel(NS_ERROR_ABORT);
142 mDNSRequest = nsnull;
146 NS_IMETHODIMP
147 PendingPACQuery::OnLookupComplete(nsICancelable *request,
148 nsIDNSRecord *record,
149 nsresult status)
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.
157 if (!mCallback)
158 return NS_OK;
160 // We're no longer pending, so we can remove ourselves.
161 PR_REMOVE_LINK(this);
162 NS_RELEASE_THIS();
164 nsCAutoString pacString;
165 status = mPACMan->GetProxyForURI(mURI, pacString);
166 Complete(status, pacString);
167 return NS_OK;
170 //-----------------------------------------------------------------------------
172 nsPACMan::nsPACMan()
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");
188 void
189 nsPACMan::Shutdown()
191 CancelExistingLoad();
192 ProcessPendingQ(NS_ERROR_ABORT);
194 mPAC = nsnull;
195 mShutdown = PR_TRUE;
198 nsresult
199 nsPACMan::GetProxyForURI(nsIURI *uri, nsACString &result)
201 NS_ENSURE_STATE(!mShutdown);
203 if (IsPACURI(uri)) {
204 result.Truncate();
205 return NS_OK;
208 MaybeReloadPAC();
210 if (IsLoading())
211 return NS_ERROR_IN_PROGRESS;
212 if (!mPAC)
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);
222 nsresult
223 nsPACMan::AsyncGetProxyForURI(nsIURI *uri, nsPACManCallback *callback)
225 NS_ENSURE_STATE(!mShutdown);
227 MaybeReloadPAC();
229 PendingPACQuery *query = new PendingPACQuery(this, uri, callback);
230 if (!query)
231 return NS_ERROR_OUT_OF_MEMORY;
232 NS_ADDREF(query);
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))
241 return NS_OK;
243 nsresult rv = query->Start();
244 if (NS_FAILED(rv)) {
245 NS_WARNING("failed to start PAC query");
246 PR_REMOVE_LINK(query);
247 NS_RELEASE(query);
250 return rv;
253 nsresult
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.
269 if (!mLoadPending) {
270 nsCOMPtr<nsIRunnable> event =
271 NS_NEW_RUNNABLE_METHOD(nsPACMan, this, StartLoading);
272 nsresult rv;
273 if (NS_FAILED(rv = NS_DispatchToCurrentThread(event)))
274 return rv;
275 mLoadPending = PR_TRUE;
278 CancelExistingLoad();
280 mLoader = loader;
281 if (pacURI) {
282 mPACURI = pacURI;
283 mLoadFailureCount = 0; // reset
285 mScheduledReload = LL_MAXINT;
286 mPAC = nsnull;
287 return NS_OK;
290 void
291 nsPACMan::StartLoading()
293 mLoadPending = PR_FALSE;
295 // CancelExistingLoad was called...
296 if (!mLoader) {
297 ProcessPendingQ(NS_ERROR_ABORT);
298 return;
301 if (NS_SUCCEEDED(mLoader->Init(this))) {
302 // Always hit the origin server when loading PAC.
303 nsCOMPtr<nsIIOService> ios = do_GetIOService();
304 if (ios) {
305 nsCOMPtr<nsIChannel> channel;
307 // NOTE: This results in GetProxyForURI being called
308 ios->NewChannelFromURI(mPACURI, getter_AddRefs(channel));
310 if (channel) {
311 channel->SetLoadFlags(nsIRequest::LOAD_BYPASS_CACHE);
312 channel->SetNotificationCallbacks(this);
313 if (NS_SUCCEEDED(channel->AsyncOpen(mLoader, nsnull)))
314 return;
319 CancelExistingLoad();
320 ProcessPendingQ(NS_ERROR_UNEXPECTED);
323 void
324 nsPACMan::MaybeReloadPAC()
326 if (!mPACURI)
327 return;
329 if (PR_Now() > mScheduledReload)
330 LoadPACFromURI(nsnull);
333 void
334 nsPACMan::OnLoadFailure()
336 PRInt32 minInterval = 5; // 5 seconds
337 PRInt32 maxInterval = 300; // 5 minutes
339 nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
340 if (prefs) {
341 prefs->GetIntPref("network.proxy.autoconfig_retry_interval_min",
342 &minInterval);
343 prefs->GetIntPref("network.proxy.autoconfig_retry_interval_max",
344 &maxInterval);
347 PRInt32 interval = minInterval << mLoadFailureCount++; // seconds
348 if (!interval || interval > maxInterval)
349 interval = maxInterval;
351 #ifdef DEBUG
352 printf("PAC load failure: will retry in %d seconds\n", interval);
353 #endif
355 mScheduledReload = PR_Now() + PRInt64(interval) * PR_USEC_PER_SEC;
358 void
359 nsPACMan::CancelExistingLoad()
361 if (mLoader) {
362 nsCOMPtr<nsIRequest> request;
363 mLoader->GetRequest(getter_AddRefs(request));
364 if (request)
365 request->Cancel(NS_ERROR_ABORT);
366 mLoader = nsnull;
370 void
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
380 // necessary).
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());
387 NS_RELEASE(query);
392 NS_IMPL_ISUPPORTS3(nsPACMan, nsIStreamLoaderObserver, nsIInterfaceRequestor,
393 nsIChannelEventSink)
395 NS_IMETHODIMP
396 nsPACMan::OnStreamComplete(nsIStreamLoader *loader,
397 nsISupports *context,
398 nsresult status,
399 PRUint32 dataLen,
400 const PRUint8 *data)
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)
408 return NS_OK;
411 mLoader = nsnull;
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);
420 if (channel) {
421 nsCOMPtr<nsIURI> uri;
422 channel->GetURI(getter_AddRefs(uri));
423 if (uri)
424 uri->GetAsciiSpec(pacURI);
428 if (!mPAC) {
429 mPAC = do_CreateInstance(NS_PROXYAUTOCONFIG_CONTRACTID, &status);
430 if (!mPAC)
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
442 // data for it.
443 mLoadFailureCount = 0;
444 } else {
445 // We were unable to load the PAC file (presumably because of a network
446 // failure). Try again a little later.
447 OnLoadFailure();
450 // Reset mPAC if necessary
451 if (mPAC && NS_FAILED(status))
452 mPAC = nsnull;
454 ProcessPendingQ(status);
455 return NS_OK;
458 NS_IMETHODIMP
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))) {
468 NS_ADDREF_THIS();
469 *result = static_cast<nsIChannelEventSink *>(this);
470 return NS_OK;
473 return NS_ERROR_NO_INTERFACE;
476 NS_IMETHODIMP
477 nsPACMan::OnChannelRedirect(nsIChannel *oldChannel, nsIChannel *newChannel,
478 PRUint32 flags)
480 return newChannel->GetURI(getter_AddRefs(mPACURI));