bug 599295 r=jduell
authorPatrick McManus <mcmanus@ducksong.com>
Wed, 02 Nov 2011 17:43:27 -0400
changeset 80988 151497ce74af21b2f353556114c647b6a1bd0774
parent 80987 285d90ff1d038d2c06f815615c3e3a802cc6f6c3
child 80989 bc3fe56d732db18c539cdc98e77c79a4cf0a65e0
push id434
push userclegnitto@mozilla.com
push dateWed, 21 Dec 2011 12:10:54 +0000
treeherdermozilla-beta@bddb6ed8dd47 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjduell
bugs599295
milestone10.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
bug 599295 r=jduell
build/automation.py.in
build/pgo/server-locations.txt
content/base/test/chrome/Makefile.in
content/base/test/chrome/test_bug599295.html
netwerk/protocol/http/nsHttpChannel.cpp
testing/mochitest/ssltunnel/ssltunnel.cpp
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -579,29 +579,36 @@ user_pref("camino.use_system_proxy_setti
 
     # Configure automatic certificate and bind custom certificates, client authentication
     locations = self.readLocations()
     locations.pop(0)
     for loc in locations:
       if loc.scheme == "https" and "nocert" not in loc.options:
         customCertRE = re.compile("^cert=(?P<nickname>[0-9a-zA-Z_ ]+)")
         clientAuthRE = re.compile("^clientauth=(?P<clientauth>[a-z]+)")
+        redirRE      = re.compile("^redir=(?P<redirhost>[0-9a-zA-Z_ .]+)")
         for option in loc.options:
           match = customCertRE.match(option)
           if match:
             customcert = match.group("nickname");
             sslTunnelConfig.write("listen:%s:%s:%s:%s\n" %
                       (loc.host, loc.port, self.sslPort, customcert))
 
           match = clientAuthRE.match(option)
           if match:
             clientauth = match.group("clientauth");
             sslTunnelConfig.write("clientauth:%s:%s:%s:%s\n" %
                       (loc.host, loc.port, self.sslPort, clientauth))
 
+          match = redirRE.match(option)
+          if match:
+            redirhost = match.group("redirhost")
+            sslTunnelConfig.write("redirhost:%s:%s:%s:%s\n" %
+                      (loc.host, loc.port, self.sslPort, redirhost))
+
     sslTunnelConfig.close()
 
     # Pre-create the certification database for the profile
     env = self.environment(xrePath = xrePath)
     certutil = os.path.join(utilityPath, "certutil" + self.BIN_SUFFIX)
     pk12util = os.path.join(utilityPath, "pk12util" + self.BIN_SUFFIX)
 
     status = self.Process([certutil, "-N", "-d", profileDir, "-f", pwfilePath], env = env).wait()
--- a/build/pgo/server-locations.txt
+++ b/build/pgo/server-locations.txt
@@ -47,17 +47,17 @@
 # and a comma-separated list of options (if indeed any options are needed).
 #
 # The format of an origin is, referring to RFC 2396, a scheme (either "http" or
 # "https"), followed by "://", followed by a host, followed by ":", followed by
 # a port number.  The colon and port number must be present even if the port
 # number is the default for the protocol.
 #
 # Unrecognized options are ignored.  Recognized options are "primary" and
-# "privileged", "nocert", "cert=some_cert_nickname". 
+# "privileged", "nocert", "cert=some_cert_nickname", "redir=hostname".
 #
 # "primary" denotes a location which is the canonical location of
 # the server; this location is the one assumed for requests which don't
 # otherwise identify a particular origin (e.g. HTTP/1.0 requests).  
 #
 # "privileged" denotes a location which should have the ability to request 
 # elevated privileges; the default is no privileges.
 #
@@ -66,16 +66,22 @@
 #
 # "cert=nickname" tells the pgo server to use a particular certificate
 # for this host. The certificate is referenced by its nickname that must
 # not contain any spaces. The certificate  key files (PKCS12 modules)
 # for custom certification are loaded from build/pgo/ssltunnel/certs
 # directory. When new certificate is added to this dir pgo/ssltunnel
 # must be builded then.
 #
+# "redir=hostname" tells the pgo server is only used for https://
+# hosts while processing the CONNECT tunnel request. It responds
+# to the CONNECT with a 302 and redirection to the hostname instead
+# of connecting to the real back end and replying with a 200. This
+# mode exists primarily to ensure we don't allow a proxy to do that.
+#
 
 #
 # This is the primary location from which tests run.
 #
 http://mochi.test:8888   primary,privileged
 
 #
 # These are a common set of prefixes scattered across one TLD with two ports and
@@ -170,8 +176,15 @@ https://sub.sectest1.example.org:443
 #
 # Used while testing the url-classifier
 #
 http://malware.example.com:80
 
 # Bug 483437, 484111
 https://www.bank1.com:443           privileged,cert=escapeattack1
 https://www.bank2.com:443           privileged,cert=escapeattack2
+
+#
+# CONNECT for redirproxy results in a 302 redirect to
+# test1.example.com
+#
+https://redirproxy.example.com:443          privileged,redir=test1.example.com
+
--- a/content/base/test/chrome/Makefile.in
+++ b/content/base/test/chrome/Makefile.in
@@ -65,15 +65,16 @@ include $(topsrcdir)/config/rules.mk
     test_bug635835.xul \
     test_fileconstructor.xul \
     fileconstructor_file.png \
     test_bug339494.xul \
     test_bug357450.xul \
     test_bug571390.xul \
     test_bug574596.html \
     test_bug683852.xul \
+    test_bug599295.html \
     $(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
 
 libs:: $(_CHROME_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/chrome/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/content/base/test/chrome/test_bug599295.html
@@ -0,0 +1,81 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=599295
+-->
+<head>
+  <title>Test for Bug 599295</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript"  src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript"  src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=599295">Mozilla Bug 599295</a>
+<style type="text/css">
+#link1 a { -moz-user-select:none; }
+</style>
+<div id="link1"><a href="http://www.mozilla.org/">link1</a></div>
+<div id="link2"><a href="http://www.mozilla.org/">link2</a></div>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 599295 **/
+
+/* Do not allow a response to a CONNECT method, used to establish an
+   SSL tunnel over an HTTP proxy, to contain a redirect */
+
+const BinaryInputStream = 
+    Components.Constructor("@mozilla.org/binaryinputstream;1",
+                           "nsIBinaryInputStream",
+                           "setInputStream");
+var listener = {
+ _httpstatus : 0,
+
+ onStartRequest: function(request, context) {
+   request.QueryInterface(Components.interfaces.nsIHttpChannel);
+   _httpstatus = request.responseStatus;
+ },
+
+ onDataAvailable: function(request, context, stream, offset, count) {
+   new BinaryInputStream(stream).readByteArray(count);
+ },
+
+ onStopRequest: function(request, context, status) {
+  /* testing here that the redirect was not followed. If it was followed
+     we would see a http status of 200 and status of NS_OK */
+
+   is(_httpstatus, 302, "http status 302");
+   is(status, Components.results.NS_ERROR_CONNECTION_REFUSED, "raised refused");
+   SimpleTest.finish();
+  }
+};
+
+function runTest() {
+  var ios = Components.classes["@mozilla.org/network/io-service;1"].
+            getService(Components.interfaces.nsIIOService);
+  var uri = ios.newURI("https://redirproxy.example.com/test", "",  null);
+  var channel = ios.newChannelFromURI(uri);
+
+  /* Previously, necko would allow a 302 as part of a CONNECT response
+     if the LOAD_DOCUMENT_URI flag was set and the original document
+     URI had not yet been changed. */
+ 
+  channel.loadFlags |= Components.interfaces.nsIChannel.LOAD_DOCUMENT_URI;
+  channel.QueryInterface(Components.interfaces.nsIHttpChannelInternal);
+  channel.documentURI = uri;
+  channel.asyncOpen(listener, null);
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTest);
+
+</script>
+</pre>
+</body>
+</html>
+
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -858,28 +858,18 @@ nsHttpChannel::ProcessFailedSSLConnect(P
     CallOnStartRequest();
     return rv;
 }
 
 bool
 nsHttpChannel::ShouldSSLProxyResponseContinue(PRUint32 httpStatus)
 {
     // When SSL connect has failed, allow proxy reply to continue only if it's
-    // an auth request, or a redirect of a non-POST top-level document load.
-    switch (httpStatus) {
-    case 407:
-        return true;
-    case 300: case 301: case 302: case 303: case 307:
-      {
-        return ( (mLoadFlags & nsIChannel::LOAD_DOCUMENT_URI) &&
-                 mURI == mDocumentURI &&
-                 mRequestHead.Method() != nsHttp::Post);
-      }
-    }
-    return false;
+    // a 407 (proxy authentication required) response
+    return (httpStatus == 407);
 }
 
 /**
  * Decide whether or not to remember Strict-Transport-Security, and whether
  * or not to enforce channel integrity.
  *
  * @return NS_ERROR_FAILURE if there's security information missing even though
  *             it's an HTTPS connection.
--- a/testing/mochitest/ssltunnel/ssltunnel.cpp
+++ b/testing/mochitest/ssltunnel/ssltunnel.cpp
@@ -181,16 +181,17 @@ enum client_auth_option {
 };
 
 // Structs for passing data into jobs on the thread pool
 typedef struct {
   PRInt32 listen_port;
   string cert_nickname;
   PLHashTable* host_cert_table;
   PLHashTable* host_clientauth_table;
+  PLHashTable* host_redir_table;
 } server_info_t;
 
 typedef struct {
   PRFileDesc* client_sock;
   PRNetAddr client_addr;
   server_info_t* server_info;
   // the original host in the Host: header for this connection is
   // stored here, for proxied connections
@@ -325,17 +326,17 @@ void SignalShutdown()
 {
   PR_Lock(shutdown_lock);
   PR_NotifyCondVar(shutdown_condvar);
   PR_Unlock(shutdown_lock);
 }
 
 bool ReadConnectRequest(server_info_t* server_info, 
     relayBuffer& buffer, PRInt32* result, string& certificate,
-    client_auth_option* clientauth, string& host)
+    client_auth_option* clientauth, string& host, string& location)
 {
   if (buffer.present() < 4) {
     LOG_DEBUG((" !! only %d bytes present in the buffer", (int)buffer.present()));
     return false;
   }
   if (strncmp(buffer.buffertail-4, "\r\n\r\n", 4)) {
     LOG_ERRORD((" !! request is not tailed with CRLFCRLF but with %x %x %x %x", 
                *(buffer.buffertail-4),
@@ -370,23 +371,27 @@ bool ReadConnectRequest(server_info_t* s
   host += token;
 
   c = PL_HashTableLookup(server_info->host_clientauth_table, token);
   if (c)
     *clientauth = *static_cast<client_auth_option*>(c);
   else
     *clientauth = caNone;
 
+  void *redir = PL_HashTableLookup(server_info->host_redir_table, token);
+  if (redir)
+    location = static_cast<char*>(redir);
+
   token = strtok2(_caret, "/", &_caret);
   if (strcmp(token, "HTTP")) {  
     LOG_ERRORD((" not tailed with HTTP but with %s", token));
     return true;
   }
 
-  *result = 200;
+  *result = (redir) ? 302 : 200;
   return true;
 }
 
 bool ConfigureSSLServerSocket(PRFileDesc* socket, server_info_t* si, string &certificate, client_auth_option clientAuth)
 {
   const char* certnick = certificate.empty() ?
       si->cert_nickname.c_str() : certificate.c_str();
 
@@ -608,16 +613,17 @@ void HandleConnection(void* data)
 
   AutoFD other_sock(PR_NewTCPSocket());
   bool client_done = false;
   bool client_error = false;
   bool connect_accepted = !do_http_proxy;
   bool ssl_updated = !do_http_proxy;
   bool expect_request_start = do_http_proxy;
   string certificateToUse;
+  string locationHeader;
   client_auth_option clientAuth;
   string fullHost;
 
   LOG_DEBUG(("SSLTUNNEL(%p)): incoming connection csock(0)=%p, ssock(1)=%p\n",
          static_cast<void*>(data),
          static_cast<void*>(ci->client_sock),
          static_cast<void*>(other_sock)));
   if (other_sock) 
@@ -742,17 +748,17 @@ void HandleConnection(void* data)
             }
 
             buffers[s].buffertail += bytesRead;
             LOG_DEBUG((", read %d bytes", bytesRead));
 
             // We have to accept and handle the initial CONNECT request here
             PRInt32 response;
             if (!connect_accepted && ReadConnectRequest(ci->server_info, buffers[s],
-                &response, certificateToUse, &clientAuth, fullHost))
+                &response, certificateToUse, &clientAuth, fullHost, locationHeader))
             {
               // Mark this as a proxy-only connection (no SSL) if the CONNECT
               // request didn't come for port 443 or from any of the server's
               // cert or clientauth hostnames.
               if (fullHost.find(":443") == string::npos)
               {
                 server_match_t match;
                 match.fullHost = fullHost;
@@ -771,29 +777,43 @@ void HandleConnection(void* data)
               }
 
               // Clean the request as it would be read
               buffers[s].bufferhead = buffers[s].buffertail = buffers[s].buffer;
               in_flags |= PR_POLL_WRITE;
               connect_accepted = true;
 
               // Store response to the oposite buffer
-              if (response != 200)
+              if (response == 200)
+              {
+                  LOG_DEBUG((" accepted CONNECT request, connected to the server, sending OK to the client\n"));
+                  strcpy(buffers[s2].buffer, "HTTP/1.1 200 Connected\r\nConnection: keep-alive\r\n\r\n");
+              }
+              else if (response == 302)
+              {
+                  LOG_DEBUG((" accepted CONNECT request with redirection, "
+                             "sending location and 302 to the client\n"));
+                  client_done = true;
+                  sprintf(buffers[s2].buffer, 
+                          "HTTP/1.1 302 Moved\r\n"
+                          "Location: https://%s/\r\n"
+                          "Connection: close\r\n\r\n",
+                          locationHeader.c_str());
+              }
+              else
               {
                 LOG_ERRORD((" could not read the connect request, closing connection with %d", response));
                 client_done = true;
                 sprintf(buffers[s2].buffer, "HTTP/1.1 %d ERROR\r\nConnection: close\r\n\r\n", response);
-                buffers[s2].buffertail = buffers[s2].buffer + strlen(buffers[s2].buffer);
+
                 break;
               }
 
-              strcpy(buffers[s2].buffer, "HTTP/1.1 200 Connected\r\nConnection: keep-alive\r\n\r\n");
               buffers[s2].buffertail = buffers[s2].buffer + strlen(buffers[s2].buffer);
 
-              LOG_DEBUG((" accepted CONNECT request, connected to the server, sending OK to the client\n"));
               // Send the response to the client socket
               break;
             } // end of CONNECT handling
 
             if (!buffers[s].areafree())
             {
               // Do not poll for read when the buffer is full
               LOG_DEBUG((" no place in our read buffer, stop reading"));
@@ -1101,16 +1121,22 @@ int processConfigLine(char* configLine)
         return 1;
       }
       server.host_clientauth_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, ClientAuthValueComparator, NULL, NULL);
       if (!server.host_clientauth_table)
       {
         LOG_ERROR(("Internal, could not create hash table\n"));
         return 1;
       }
+      server.host_redir_table = PL_NewHashTable(0, PL_HashString, PL_CompareStrings, PL_CompareStrings, NULL, NULL);
+      if (!server.host_redir_table)
+      {
+        LOG_ERROR(("Internal, could not create hash table\n"));
+        return 1;
+      }
       servers.push_back(server);
     }
 
     return 0;
   }
   
   if (!strcmp(keyword, "clientauth"))
   {
@@ -1167,16 +1193,61 @@ int processConfigLine(char* configLine)
     {
       LOG_ERROR(("Server on port %d for client authentication option is not defined, use 'listen' option first", port));
       return 1;
     }
 
     return 0;
   }
 
+  if (!strcmp(keyword, "redirhost"))
+  {
+    char* hostname = strtok2(_caret, ":", &_caret);
+    char* hostportstring = strtok2(_caret, ":", &_caret);
+    char* serverportstring = strtok2(_caret, ":", &_caret);
+
+    int port = atoi(serverportstring);
+    if (port <= 0) {
+      LOG_ERROR(("Invalid port specified: %s\n", serverportstring));
+      return 1;
+    }
+
+    if (server_info_t* existingServer = findServerInfo(port))
+    {
+      char* redirhoststring = strtok2(_caret, ":", &_caret);
+
+      any_host_spec_config = true;
+
+      char *hostname_copy = new char[strlen(hostname)+strlen(hostportstring)+2];
+      if (!hostname_copy) {
+        LOG_ERROR(("Out of memory"));
+        return 1;
+      }
+
+      strcpy(hostname_copy, hostname);
+      strcat(hostname_copy, ":");
+      strcat(hostname_copy, hostportstring);
+
+      char *redir_copy = new char[strlen(redirhoststring)+1];
+      strcpy(redir_copy, redirhoststring);
+      PLHashEntry* entry = PL_HashTableAdd(existingServer->host_redir_table, hostname_copy, redir_copy);
+      if (!entry) {
+        LOG_ERROR(("Out of memory"));
+        return 1;
+      }
+    }
+    else
+    {
+      LOG_ERROR(("Server on port %d for redirhost option is not defined, use 'listen' option first", port));
+      return 1;
+    }
+
+    return 0;
+  }
+
   // Configure the NSS certificate database directory
   if (!strcmp(keyword, "certdbdir"))
   {
     nssconfigdir = strtok2(_caret, "\n", &_caret);
     return 0;
   }
 
   LOG_ERROR(("Error: keyword \"%s\" unexpected\n", keyword));
@@ -1227,16 +1298,23 @@ int parseConfigFile(const char* filePath
 
 PRIntn freeHostCertHashItems(PLHashEntry *he, PRIntn i, void *arg)
 {
   delete [] (char*)he->key;
   delete [] (char*)he->value;
   return HT_ENUMERATE_REMOVE;
 }
 
+PRIntn freeHostRedirHashItems(PLHashEntry *he, PRIntn i, void *arg)
+{
+  delete [] (char*)he->key;
+  delete [] (char*)he->value;
+  return HT_ENUMERATE_REMOVE;
+}
+
 PRIntn freeClientAuthHashItems(PLHashEntry *he, PRIntn i, void *arg)
 {
   delete [] (char*)he->key;
   delete (client_auth_option*)he->value;
   return HT_ENUMERATE_REMOVE;
 }
 
 int main(int argc, char** argv)
@@ -1365,15 +1443,17 @@ int main(int argc, char** argv)
     LOG_DEBUG(("Leaked NSS objects!\n"));
   }
   
   for (vector<server_info_t>::iterator it = servers.begin();
        it != servers.end(); it++) 
   {
     PL_HashTableEnumerateEntries(it->host_cert_table, freeHostCertHashItems, NULL);
     PL_HashTableEnumerateEntries(it->host_clientauth_table, freeClientAuthHashItems, NULL);
+    PL_HashTableEnumerateEntries(it->host_redir_table, freeHostRedirHashItems, NULL);
     PL_HashTableDestroy(it->host_cert_table);
     PL_HashTableDestroy(it->host_clientauth_table);
+    PL_HashTableDestroy(it->host_redir_table);
   }
 
   PR_Cleanup();
   return 0;
 }