# HG changeset patch # User Jeff Muizelaar # Date 1225835400 18000 # Node ID 6399284b8c8e96297cc1f336cfc3c379d917a7a8 # Parent 5b0900b3e71cd926ceb375eba0e29410fe07e3fc Bug 235853 - "[PAC] Defer proxy resolution for HTTP and HTTPS PAC to avoid blocking main thread during DNS resolution" [a-1.9.1b2=mbeltzner] diff --git a/content/base/test/Makefile.in b/content/base/test/Makefile.in --- a/content/base/test/Makefile.in +++ b/content/base/test/Makefile.in @@ -206,16 +206,18 @@ include $(topsrcdir)/config/rules.mk test_bug424212.html \ test_bug425013.html \ bug426308-redirect.sjs \ test_bug426308.html \ test_bug426646.html \ file_bug426646-1.html \ file_bug426646-2.html \ test_bug429157.html \ + test_header.html \ + header.sjs \ test_XHR.html \ file_XHR_pass1.xml \ file_XHR_pass2.txt \ file_XHR_pass3.txt \ file_XHR_pass3.txt^headers^ \ file_XHR_fail1.txt \ file_XHR_fail1.txt^headers^ \ file_XHR_binary1.bin \ diff --git a/content/base/test/header.sjs b/content/base/test/header.sjs new file mode 100644 --- /dev/null +++ b/content/base/test/header.sjs @@ -0,0 +1,8 @@ +function handleRequest(request, response) { + response.setHeader("Content-Type", "text/plain", false); + response.setHeader("Cache-Control", "no-cache", false); + + var value = request.hasHeader("SomeHeader") ? request.getHeader("SomeHeader") + : ""; + response.write("SomeHeader: " + value); +} diff --git a/content/base/test/test_header.html b/content/base/test/test_header.html new file mode 100644 --- /dev/null +++ b/content/base/test/test_header.html @@ -0,0 +1,31 @@ + + + + Test for XHR header preservation + + + + + +

+ +
+
+
+ + diff --git a/extensions/cookie/test/test_loadflags.html b/extensions/cookie/test/test_loadflags.html --- a/extensions/cookie/test/test_loadflags.html +++ b/extensions/cookie/test/test_loadflags.html @@ -5,17 +5,18 @@ - +

 
 
diff --git a/netwerk/base/src/nsIOService.cpp b/netwerk/base/src/nsIOService.cpp --- a/netwerk/base/src/nsIOService.cpp +++ b/netwerk/base/src/nsIOService.cpp @@ -638,18 +638,28 @@ nsIOService::NewChannelFromURIWithProxyF if (protoFlags & nsIProtocolHandler::ALLOWS_PROXY) { nsCOMPtr pi; if (!mProxyService) { mProxyService = do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID); if (!mProxyService) NS_WARNING("failed to get protocol proxy service"); } if (mProxyService) { - rv = mProxyService->Resolve(aProxyURI ? aProxyURI : aURI, - proxyFlags, getter_AddRefs(pi)); + PRUint32 flags = 0; + if (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https")) + flags = nsIProtocolProxyService::RESOLVE_NON_BLOCKING; + rv = mProxyService->Resolve(aProxyURI ? aProxyURI : aURI, proxyFlags, + getter_AddRefs(pi)); + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + // Use an UNKNOWN proxy to defer resolution and avoid blocking. + rv = mProxyService->NewProxyInfo(NS_LITERAL_CSTRING("unknown"), + NS_LITERAL_CSTRING(""), + -1, 0, 0, nsnull, + getter_AddRefs(pi)); + } if (NS_FAILED(rv)) pi = nsnull; } if (pi) { nsCAutoString type; if (NS_SUCCEEDED(pi->GetType(type)) && type.EqualsLiteral("http")) { // we are going to proxy this channel using an http proxy rv = GetProtocolHandler("http", getter_AddRefs(handler)); diff --git a/netwerk/base/src/nsProtocolProxyService.cpp b/netwerk/base/src/nsProtocolProxyService.cpp --- a/netwerk/base/src/nsProtocolProxyService.cpp +++ b/netwerk/base/src/nsProtocolProxyService.cpp @@ -938,17 +938,18 @@ nsProtocolProxyService::NewProxyInfo(con PRUint32 aFailoverTimeout, nsIProxyInfo *aFailoverProxy, nsIProxyInfo **aResult) { static const char *types[] = { kProxyType_HTTP, kProxyType_SOCKS, kProxyType_SOCKS4, - kProxyType_DIRECT + kProxyType_DIRECT, + kProxyType_UNKNOWN }; // resolve type; this allows us to avoid copying the type string into each // proxy info instance. we just reference the string literals directly :) const char *type = nsnull; for (PRUint32 i=0; iUsingSSL()) @@ -1672,16 +1673,31 @@ HttpBaseChannel::SetupReplacementChannel if (bag) mPropertyHash.EnumerateRead(CopyProperties, bag.get()); // transfer timed channel enabled status nsCOMPtr timed(do_QueryInterface(newChannel)); if (timed) timed->SetTimingEnabled(mTimingEnabled); + if (forProxy) { + // Transfer all the headers from the previous channel + // this is needed for any headers that are not covered by the code above + // or have been set separately. e.g. manually setting Referer without + // setting up mReferrer + PRUint32 count = mRequestHead.Headers().Count(); + for (PRUint32 i = 0; i < count; ++i) { + nsHttpAtom header; + const char *value = mRequestHead.Headers().PeekHeaderAt(i, header); + + httpChannel->SetRequestHeader(nsDependentCString(header), + nsDependentCString(value), false); + } + } + return NS_OK; } //------------------------------------------------------------------------------ } // namespace net } // namespace mozilla diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h --- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -228,17 +228,18 @@ protected: void DoNotifyListener(); virtual void DoNotifyListenerCleanup() = 0; nsresult ApplyContentConversions(); void AddCookiesToRequest(); virtual nsresult SetupReplacementChannel(nsIURI *, nsIChannel *, - bool preserveMethod); + bool preserveMethod, + bool forProxy); // Helper function to simplify getting notification callbacks. template void GetCallback(nsCOMPtr &aResult) { NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, NS_GET_TEMPLATE_IID(T), getter_AddRefs(aResult)); diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp --- a/netwerk/protocol/http/HttpChannelChild.cpp +++ b/netwerk/protocol/http/HttpChannelChild.cpp @@ -751,17 +751,17 @@ HttpChannelChild::Redirect1Begin(const P // We won't get OnStartRequest, set cookies here. mResponseHead = new nsHttpResponseHead(responseHead); SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie)); bool rewriteToGET = ShouldRewriteRedirectToGET(mResponseHead->Status(), mRequestHead.Method()); - rv = SetupReplacementChannel(uri, newChannel, !rewriteToGET); + rv = SetupReplacementChannel(uri, newChannel, !rewriteToGET, false); if (NS_FAILED(rv)) { // Veto redirect. nsHttpChannel decides to cancel or continue. OnRedirectVerifyCallback(rv); return; } mRedirectChannelChild = do_QueryInterface(newChannel); if (mRedirectChannelChild) { diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -1420,17 +1420,17 @@ nsHttpChannel::AsyncRedirectChannelToHtt nsCOMPtr ioService; rv = gHttpHandler->GetIOService(getter_AddRefs(ioService)); NS_ENSURE_SUCCESS(rv, rv); rv = ioService->NewChannelFromURI(upgradedURI, getter_AddRefs(newChannel)); NS_ENSURE_SUCCESS(rv, rv); - rv = SetupReplacementChannel(upgradedURI, newChannel, true); + rv = SetupReplacementChannel(upgradedURI, newChannel, true, false); NS_ENSURE_SUCCESS(rv, rv); // Inform consumers about this fake redirect mRedirectChannel = newChannel; PRUint32 flags = nsIChannelEventSink::REDIRECT_PERMANENT; PushRedirectAsyncFunc( &nsHttpChannel::ContinueAsyncRedirectChannelToHttps); @@ -1517,17 +1517,17 @@ nsHttpChannel::AsyncDoReplaceWithProxy(n LOG(("nsHttpChannel::AsyncDoReplaceWithProxy [this=%p pi=%p]", this, pi)); nsresult rv; nsCOMPtr newChannel; rv = gHttpHandler->NewProxiedChannel(mURI, pi, getter_AddRefs(newChannel)); if (NS_FAILED(rv)) return rv; - rv = SetupReplacementChannel(mURI, newChannel, true); + rv = SetupReplacementChannel(mURI, newChannel, true, true); if (NS_FAILED(rv)) return rv; // Inform consumers about this fake redirect mRedirectChannel = newChannel; PRUint32 flags = nsIChannelEventSink::REDIRECT_INTERNAL; PushRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy); @@ -1939,17 +1939,17 @@ nsHttpChannel::ProcessFallback(bool *wai if (mCacheEntry) CloseCacheEntry(true); // Create a new channel to load the fallback entry. nsRefPtr newChannel; rv = gHttpHandler->NewChannel(mURI, getter_AddRefs(newChannel)); NS_ENSURE_SUCCESS(rv, rv); - rv = SetupReplacementChannel(mURI, newChannel, true); + rv = SetupReplacementChannel(mURI, newChannel, true, false); NS_ENSURE_SUCCESS(rv, rv); // Make sure the new channel loads from the fallback key. nsCOMPtr httpInternal = do_QueryInterface(newChannel, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = httpInternal->SetupFallbackChannel(mFallbackKey.get()); @@ -3328,23 +3328,25 @@ nsHttpChannel::ClearBogusContentEncoding //----------------------------------------------------------------------------- // nsHttpChannel //----------------------------------------------------------------------------- nsresult nsHttpChannel::SetupReplacementChannel(nsIURI *newURI, nsIChannel *newChannel, - bool preserveMethod) + bool preserveMethod, + bool forProxy) { LOG(("nsHttpChannel::SetupReplacementChannel " "[this=%p newChannel=%p preserveMethod=%d]", this, newChannel, preserveMethod)); - nsresult rv = HttpBaseChannel::SetupReplacementChannel(newURI, newChannel, preserveMethod); + nsresult rv = HttpBaseChannel::SetupReplacementChannel(newURI, newChannel, + preserveMethod, forProxy); if (NS_FAILED(rv)) return rv; nsCOMPtr httpChannel = do_QueryInterface(newChannel); if (!httpChannel) return NS_OK; // no other options to set // convey the mApplyConversion flag (bug 91862) @@ -3357,16 +3359,36 @@ nsHttpChannel::SetupReplacementChannel(n nsCOMPtr resumableChannel(do_QueryInterface(newChannel)); if (!resumableChannel) { NS_WARNING("Got asked to resume, but redirected to non-resumable channel!"); return NS_ERROR_NOT_RESUMABLE; } resumableChannel->ResumeAt(mStartPos, mEntityID); } + if (forProxy) { + // Transfer the cache info to the new channel, if needed. + nsCOMPtr cachingChannel = do_QueryInterface(newChannel); + if (cachingChannel) { + // cacheKey is just mPostID wrapped in an nsISupportsPRUint32, + // we don't need to transfer it if it's 0. + if (mPostID) { + nsCOMPtr cacheKey; + GetCacheKey(getter_AddRefs(cacheKey)); + if (cacheKey) { + cachingChannel->SetCacheKey(cacheKey); + } + } + + // cacheClientID, cacheForOfflineUse + cachingChannel->SetOfflineCacheClientID(mOfflineCacheClientID); + cachingChannel->SetCacheForOfflineUse(mCacheForOfflineUse); + } + } + return NS_OK; } nsresult nsHttpChannel::AsyncProcessRedirection(PRUint32 redirectType) { LOG(("nsHttpChannel::AsyncProcessRedirection [this=%p type=%u]\n", this, redirectType)); @@ -3479,17 +3501,17 @@ nsHttpChannel::ContinueProcessRedirectio nsCOMPtr ioService; rv = gHttpHandler->GetIOService(getter_AddRefs(ioService)); if (NS_FAILED(rv)) return rv; nsCOMPtr newChannel; rv = ioService->NewChannelFromURI(mRedirectURI, getter_AddRefs(newChannel)); if (NS_FAILED(rv)) return rv; - rv = SetupReplacementChannel(mRedirectURI, newChannel, !rewriteToGET); + rv = SetupReplacementChannel(mRedirectURI, newChannel, !rewriteToGET, false); if (NS_FAILED(rv)) return rv; PRUint32 redirectFlags; if (mRedirectType == 301) // Moved Permanently redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT; else redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY; diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h --- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -189,17 +189,19 @@ private: // redirection specific methods void HandleAsyncRedirect(); nsresult ContinueHandleAsyncRedirect(nsresult); void HandleAsyncNotModified(); void HandleAsyncFallback(); nsresult ContinueHandleAsyncFallback(nsresult); nsresult PromptTempRedirect(); - virtual nsresult SetupReplacementChannel(nsIURI *, nsIChannel *, bool preserveMethod); + virtual nsresult SetupReplacementChannel(nsIURI *, nsIChannel *, + bool preserveMethod, + bool forProxy); // proxy specific methods nsresult ProxyFailover(); nsresult AsyncDoReplaceWithProxy(nsIProxyInfo *); nsresult ContinueDoReplaceWithProxy(nsresult); void HandleAsyncReplaceWithProxy(); nsresult ContinueHandleAsyncReplaceWithProxy(nsresult); nsresult ResolveProxy(); diff --git a/netwerk/test/unit/test_proxy_preservation_bug235853.js b/netwerk/test/unit/test_proxy_preservation_bug235853.js new file mode 100644 --- /dev/null +++ b/netwerk/test/unit/test_proxy_preservation_bug235853.js @@ -0,0 +1,139 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is Google Inc. + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Jeff Muizelaar + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +do_import_script("netwerk/test/httpserver/httpd.js"); + +var ios = Components.classes["@mozilla.org/network/io-service;1"] + .getService(Components.interfaces.nsIIOService); + +var prefs = Components.classes["@mozilla.org/preferences-service;1"] + .getService(Components.interfaces.nsIPrefBranch); + +function make_channel(url) { + return ios.newChannel(url, null, null) + .QueryInterface(Components.interfaces.nsIHttpChannel); +} + +var httpserv = null; + +// respond with the value of the header +function responseHandler(request, response) { + response.setHeader("Content-Type", "text/plain", false); + + var value = request.hasHeader("SomeHeader") ? request.getHeader("SomeHeader") : ""; + response.write("SomeHeader: " + value); +} + +function run_test() { + httpserv = new nsHttpServer(); + httpserv.start(4444); + httpserv.registerPathHandler("/test", responseHandler); + // setup an identity so we can use the server with a different name when using + // the server as a proxy + httpserv.identity.add("http", "foo", 80); + + // cache key on channel creation + var orig_key; + + // setup the properties that we want to be preserved + function setup_channel(chan) { + chan.setRequestHeader("SomeHeader", "Someval", false); + + // set cache key to something other than 0 + orig_key = chan.QueryInterface(Ci.nsICachingChannel) + .cacheKey.QueryInterface(Ci.nsISupportsPRUint32); + orig_key.data = 0x32; + chan.QueryInterface(Ci.nsICachingChannel).cacheKey = orig_key; + } + + // check that these properties are preserved + function check_response(request, data) { + // check that headers are preserved + do_check_eq(data, "SomeHeader: Someval"); + + // check that the cacheKey is preserved + var key = request.QueryInterface(Ci.nsICachingChannel) + .cacheKey.QueryInterface(Ci.nsISupportsPRUint32); + do_check_eq(key.data, orig_key.data); + } + + function setup_noproxy() { + var chan = make_channel("http://localhost:4444/test"); + setup_channel(chan); + chan.asyncOpen(new ChannelListener(test_noproxy, null), null); + } + + function test_noproxy(request, data, ctx) { + check_response(request, data); + + setup_with_proxy(); + } + + function setup_with_proxy() { + // Setup a PAC rule using the server we setup as the proxy + var pac = 'data:text/plain,' + + 'function FindProxyForURL(url, host) {' + + ' return "PROXY localhost:4444";' + + '}'; + + // Configure PAC + prefs.setIntPref("network.proxy.type", 2); + prefs.setCharPref("network.proxy.autoconfig_url", pac); + + var chan = make_channel("http://foo/test"); + + setup_channel(chan); + + chan.asyncOpen(new ChannelListener(test_with_proxy, null), null); + } + + function test_with_proxy(request, data, ctx) { + check_response(request, data); + + // cleanup PAC + prefs.setCharPref("network.proxy.autoconfig_url", ""); + prefs.setIntPref("network.proxy.type", 0); + + httpserv.stop(); + do_test_finished(); + } + + setup_noproxy(); + + do_test_pending(); +} diff --git a/netwerk/test/unit/xpcshell.ini b/netwerk/test/unit/xpcshell.ini --- a/netwerk/test/unit/xpcshell.ini +++ b/netwerk/test/unit/xpcshell.ini @@ -139,16 +139,17 @@ skip-if = os == "android" [test_parse_content_type.js] [test_permmgr.js] [test_plaintext_sniff.js] [test_post.js] [test_progress.js] [test_protocolproxyservice.js] [test_proxy-failover_canceled.js] [test_proxy-failover_passing.js] +[test_proxy_preservation_bug235853.js] [test_proxy-replace_canceled.js] [test_proxy-replace_passing.js] [test_range_requests.js] [test_readline.js] [test_redirect-caching_canceled.js] [test_redirect-caching_failure.js] [test_redirect-caching_passing.js] [test_redirect_canceled.js]