Bug 235853: Defer proxy resolution for HTTP and HTTPS PAC to avoid blocking main thread during DNS resolution, original patch by shaver@mozilla.org, r+sr=biesi
☠☠ backed out by 82d032a810cd ☠ ☠
authorJeff Muizelaar <jmuizelaar@mozilla.com>
Tue, 04 Nov 2008 23:11:31 -0500
changeset 21335 f014c59ad81dceb05f52a2826023c0bd8947c211
parent 21334 f3e79a7ed0e70de20778ec024c250db0431f6dfe
child 21336 82d032a810cd7c22304653cee6d15113de7f1114
child 21506 5329c0a211d31a856eac31dfff0a276ab3d8fa70
push idunknown
push userunknown
push dateunknown
bugs235853
milestone1.9.1b2pre
Bug 235853: Defer proxy resolution for HTTP and HTTPS PAC to avoid blocking main thread during DNS resolution, original patch by shaver@mozilla.org, r+sr=biesi
content/base/test/Makefile.in
content/base/test/header.sjs
content/base/test/test_header.html
extensions/cookie/test/test_loadflags.html
netwerk/base/src/nsIOService.cpp
netwerk/base/src/nsProtocolProxyService.cpp
netwerk/protocol/http/src/nsHttpChannel.cpp
netwerk/protocol/http/src/nsHttpChannel.h
netwerk/test/unit/test_proxy_preservation_bug235853.js
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -195,16 +195,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^ \
 		test_bug428847.html \
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);
+}
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_header.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for XHR header preservation</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+    /** Test for Bug 421622 **/
+    const SJS_URL = "http://localhost:8888/tests/content/base/test/header.sjs";
+    const VALUE = "http://www.mozilla.org/";
+
+    var req = new XMLHttpRequest();
+    req.open("GET", SJS_URL, false);
+    req.setRequestHeader("SomeHeader", VALUE);
+    req.send(null);
+
+    is(req.responseText,
+       "SomeHeader: " + VALUE,
+       "Header received by server does not match what was set");
+</script>
+</pre>
+</body>
+</html>
--- a/extensions/cookie/test/test_loadflags.html
+++ b/extensions/cookie/test/test_loadflags.html
@@ -1,16 +1,16 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Test for Cross domain access to properties</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>        
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
-<body onload="setupTest('http://example.org/tests/extensions/cookie/test/file_loadflags_inner.html', 'example.org', 5, 1, 2)">
+<body onload="setupTest('http://example.org/tests/extensions/cookie/test/file_loadflags_inner.html', 'example.org', 5, 1, 4)">
 <p id="display"></p>
 <pre id="test">
 <script class="testbody" type="text/javascript" src="file_testloadflags.js">
 </script>
 </pre>
 </body>
 </html>
--- a/netwerk/base/src/nsIOService.cpp
+++ b/netwerk/base/src/nsIOService.cpp
@@ -546,17 +546,27 @@ nsIOService::NewChannelFromURI(nsIURI *a
     if (protoFlags & nsIProtocolHandler::ALLOWS_PROXY) {
         nsCOMPtr<nsIProxyInfo> pi;
         if (!mProxyService) {
             mProxyService = do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
             if (!mProxyService)
                 NS_WARNING("failed to get protocol proxy service");
         }
         if (mProxyService) {
-            rv = mProxyService->Resolve(aURI, 0, getter_AddRefs(pi));
+            PRUint32 flags = 0;
+            if (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https"))
+                flags = nsIProtocolProxyService::RESOLVE_NON_BLOCKING;
+            rv = mProxyService->Resolve(aURI, flags, 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));
--- a/netwerk/base/src/nsProtocolProxyService.cpp
+++ b/netwerk/base/src/nsProtocolProxyService.cpp
@@ -919,17 +919,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; i<NS_ARRAY_LENGTH(types); ++i) {
         if (aType.LowerCaseEqualsASCII(types[i])) {
             type = types[i];
--- a/netwerk/protocol/http/src/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/src/nsHttpChannel.cpp
@@ -1117,17 +1117,17 @@ nsHttpChannel::DoReplaceWithProxy(nsIPro
 {
     nsresult rv;
 
     nsCOMPtr<nsIChannel> newChannel;
     rv = gHttpHandler->NewProxiedChannel(mURI, pi, getter_AddRefs(newChannel));
     if (NS_FAILED(rv))
         return rv;
 
-    rv = SetupReplacementChannel(mURI, newChannel, PR_TRUE);
+    rv = SetupReplacementChannel(mURI, newChannel, PR_TRUE, PR_TRUE);
     if (NS_FAILED(rv))
         return rv;
 
     // Inform consumers about this fake redirect
     PRUint32 flags = nsIChannelEventSink::REDIRECT_INTERNAL;
     rv = gHttpHandler->OnChannelRedirect(this, newChannel, flags);
     if (NS_FAILED(rv))
         return rv;
@@ -1436,17 +1436,17 @@ nsHttpChannel::ProcessFallback(PRBool *f
     if (mCacheEntry)
         CloseCacheEntry(PR_TRUE);
 
     // Create a new channel to load the fallback entry.
     nsRefPtr<nsIChannel> newChannel;
     rv = gHttpHandler->NewChannel(mURI, getter_AddRefs(newChannel));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = SetupReplacementChannel(mURI, newChannel, PR_TRUE);
+    rv = SetupReplacementChannel(mURI, newChannel, PR_TRUE, PR_FALSE);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Make sure the new channel loads from the fallback key.
     nsCOMPtr<nsIHttpChannelInternal> httpInternal =
         do_QueryInterface(newChannel, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = httpInternal->SetupFallbackChannel(mFallbackKey.get());
@@ -2555,17 +2555,18 @@ CopyProperties(const nsAString& aKey, ns
                                              (aClosure);
     bag->SetProperty(aKey, aData);
     return PL_DHASH_NEXT;
 }
 
 nsresult
 nsHttpChannel::SetupReplacementChannel(nsIURI       *newURI, 
                                        nsIChannel   *newChannel,
-                                       PRBool        preserveMethod)
+                                       PRBool        preserveMethod,
+                                       PRBool        forProxy)
 {
     PRUint32 newLoadFlags = mLoadFlags | LOAD_REPLACE;
     // if the original channel was using SSL and this channel is not using
     // SSL, then no need to inhibit persistent caching.  however, if the
     // original channel was not using SSL and has INHIBIT_PERSISTENT_CACHING
     // set, then allow the flag to apply to the redirected channel as well.
     // since we force set INHIBIT_PERSISTENT_CACHING on all HTTPS channels,
     // we only need to check if the original channel was using SSL.
@@ -2606,16 +2607,17 @@ nsHttpChannel::SetupReplacementChannel(n
         }
         // must happen after setting upload stream since SetUploadStream
         // may change the request method.
         httpChannel->SetRequestMethod(nsDependentCString(mRequestHead.Method()));
     }
     // convey the referrer if one was used for this channel to the next one
     if (mReferrer)
         httpChannel->SetReferrer(mReferrer);
+
     // convey the mAllowPipelining flag
     httpChannel->SetAllowPipelining(mAllowPipelining);
     // convey the new redirection limit
     httpChannel->SetRedirectionLimit(mRedirectionLimit - 1);
 
     nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(newChannel);
     if (httpInternal) {
         // update the DocumentURI indicator since we are being redirected.
@@ -2652,16 +2654,49 @@ nsHttpChannel::SetupReplacementChannel(n
         // We purposely avoid transfering mChooseApplicationCache.
     }
 
     // transfer any properties
     nsCOMPtr<nsIWritablePropertyBag> bag(do_QueryInterface(newChannel));
     if (bag)
         mPropertyHash.EnumerateRead(CopyProperties, bag.get());
 
+    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), PR_FALSE);
+      }
+
+      // Transfer the cache info to the new channel, if needed.
+      nsCOMPtr<nsICachingChannel> 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<nsISupports> cacheKey;
+          GetCacheKey(getter_AddRefs(cacheKey));
+          if (cacheKey) {
+            cachingChannel->SetCacheKey(cacheKey);
+          }
+        }
+
+        // cacheClientID, cacheForOfflineUse
+        cachingChannel->SetOfflineCacheClientID(mOfflineCacheClientID);
+        cachingChannel->SetCacheForOfflineUse(mCacheForOfflineUse);
+      }
+    }
+
     return NS_OK;
 }
 
 nsresult
 nsHttpChannel::ProcessRedirection(PRUint32 redirectType)
 {
     LOG(("nsHttpChannel::ProcessRedirection [this=%x type=%u]\n",
         this, redirectType));
@@ -2737,17 +2772,17 @@ nsHttpChannel::ProcessRedirection(PRUint
     if (preserveMethod && mUploadStream) {
         rv = PromptTempRedirect();
         if (NS_FAILED(rv)) return rv;
     }
 
     rv = ioService->NewChannelFromURI(newURI, getter_AddRefs(newChannel));
     if (NS_FAILED(rv)) return rv;
 
-    rv = SetupReplacementChannel(newURI, newChannel, preserveMethod);
+    rv = SetupReplacementChannel(newURI, newChannel, preserveMethod, PR_FALSE);
     if (NS_FAILED(rv)) return rv;
 
     PRUint32 redirectFlags;
     if (redirectType == 301) // Moved Permanently
         redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
     else
         redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
 
--- a/netwerk/protocol/http/src/nsHttpChannel.h
+++ b/netwerk/protocol/http/src/nsHttpChannel.h
@@ -173,17 +173,19 @@ private:
     nsresult ProcessFallback(PRBool *fallingBack);
     PRBool   ResponseWouldVary();
 
     // redirection specific methods
     void     HandleAsyncRedirect();
     void     HandleAsyncNotModified();
     void     HandleAsyncFallback();
     nsresult PromptTempRedirect();
-    nsresult SetupReplacementChannel(nsIURI *, nsIChannel *, PRBool preserveMethod);
+    nsresult SetupReplacementChannel(nsIURI *, nsIChannel *,
+                                     PRBool preserveMethod,
+                                     PRBool forProxy);
 
     // proxy specific methods
     nsresult ProxyFailover();
     nsresult DoReplaceWithProxy(nsIProxyInfo *);
     void HandleAsyncReplaceWithProxy();
     nsresult ResolveProxy();
 
     // cache specific methods
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 <jmuizelaar@mozilla.com>
+ *
+ * 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();
+}