bug 1037082 - HTTPS Proxying Auth (407) problems r=hurley a=lmandel
authorPatrick McManus <mcmanus@ducksong.com>
Wed, 16 Jul 2014 12:24:31 -0400
changeset 209119 abc10706ecbb83e369e2244eaf261bd55d42cf48
parent 209118 07f6a41d8542b0b32eb7e4eab6e9f0d6946882f3
child 209120 21823bcb1ce9eb600b18918e90bc300d98ed3e0f
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershurley, lmandel
bugs1037082
milestone32.0a2
bug 1037082 - HTTPS Proxying Auth (407) problems r=hurley a=lmandel
netwerk/protocol/http/Http2Session.cpp
netwerk/protocol/http/Http2Stream.cpp
netwerk/protocol/http/Http2Stream.h
netwerk/protocol/http/SpdySession3.cpp
netwerk/protocol/http/SpdySession31.cpp
netwerk/protocol/http/SpdyStream3.cpp
netwerk/protocol/http/SpdyStream3.h
netwerk/protocol/http/SpdyStream31.cpp
netwerk/protocol/http/SpdyStream31.h
netwerk/protocol/http/TunnelUtils.cpp
netwerk/protocol/http/TunnelUtils.h
netwerk/protocol/http/nsHttpChannelAuthProvider.h
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnection.h
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
netwerk/protocol/http/nsHttpTransaction.h
--- a/netwerk/protocol/http/Http2Session.cpp
+++ b/netwerk/protocol/http/Http2Session.cpp
@@ -2876,35 +2876,40 @@ Http2Session::DispatchOnTunnel(nsAHttpTr
 
   LOG3(("Http2Session::DispatchOnTunnel %p trans=%p", this, trans));
 
   aHttpTransaction->SetConnection(nullptr);
 
   // this transaction has done its work of setting up a tunnel, let
   // the connection manager queue it if necessary
   trans->SetDontRouteViaWildCard(true);
+  trans->EnableKeepAlive();
 
   if (FindTunnelCount(ci) < gHttpHandler->MaxConnectionsPerOrigin()) {
     LOG3(("Http2Session::DispatchOnTunnel %p create on new tunnel %s",
           this, ci->HashKey().get()));
+    // The connect transaction will hold onto the underlying http
+    // transaction so that an auth created by the connect can be mappped
+    // to the correct security callbacks
     nsRefPtr<SpdyConnectTransaction> connectTrans =
       new SpdyConnectTransaction(ci, aCallbacks,
                                  trans->Caps(), trans, this);
     AddStream(connectTrans, trans->Priority(),
               false, nullptr);
     Http2Stream *tunnel = mStreamTransactionHash.Get(connectTrans);
     MOZ_ASSERT(tunnel);
     RegisterTunnel(tunnel);
+  } else {
+    // requeue it. The connection manager is responsible for actually putting
+    // this on the tunnel connection with the specific ci now that it
+    // has DontRouteViaWildCard set.
+    LOG3(("Http2Session::DispatchOnTunnel %p trans=%p queue in connection manager",
+          this, trans));
+    gHttpHandler->InitiateTransaction(trans, trans->Priority());
   }
-
-  // requeue it. The connection manager is responsible for actually putting
-  // this on the tunnel connection with the specific ci now that it
-  // has DontRouteViaWildCard set.
-  trans->EnableKeepAlive();
-  gHttpHandler->InitiateTransaction(trans, trans->Priority());
 }
 
 
 nsresult
 Http2Session::BufferOutput(const char *buf,
                            uint32_t count,
                            uint32_t *countRead)
 {
--- a/netwerk/protocol/http/Http2Stream.cpp
+++ b/netwerk/protocol/http/Http2Stream.cpp
@@ -64,16 +64,17 @@ Http2Stream::Http2Stream(nsAHttpTransact
   , mTxStreamFrameSize(0)
   , mRequestBodyLenRemaining(0)
   , mLocalUnacked(0)
   , mBlockedOnRwin(false)
   , mTotalSent(0)
   , mTotalRead(0)
   , mPushSource(nullptr)
   , mIsTunnel(false)
+  , mPlainTextTunnel(false)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
   LOG3(("Http2Stream::Http2Stream %p", this));
 
   mServerReceiveWindow = session->GetServerInitialStreamWindow();
   mClientReceiveWindow = session->PushAllowance();
 
@@ -214,17 +215,17 @@ Http2Stream::ReadSegments(nsAHttpSegment
     MOZ_ASSERT(false, "Http2Stream::ReadSegments unknown state");
     break;
   }
 
   return rv;
 }
 
 // WriteSegments() is used to read data off the socket. Generally this is
-// just a call through to the associate nsHttpTransaciton for this stream
+// just a call through to the associated nsHttpTransaction for this stream
 // for the remaining data bytes indicated by the current DATA frame.
 
 nsresult
 Http2Stream::WriteSegments(nsAHttpSegmentWriter *writer,
                            uint32_t count,
                            uint32_t *countWritten)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
@@ -857,35 +858,39 @@ Http2Stream::ConvertResponseHeaders(Http
   decompressor->GetStatus(status);
   if (status.IsEmpty()) {
     LOG3(("Http2Stream::ConvertHeaders %p Error - no status\n", this));
     return NS_ERROR_ILLEGAL_VALUE;
   }
 
   if (mIsTunnel) {
     nsresult errcode;
-    if (status.ToInteger(&errcode) != 200) {
-      LOG3(("Http2Stream %p Tunnel not 200", this));
-      return NS_ERROR_ABORT;
+
+    int32_t code = status.ToInteger(&errcode);
+    LOG3(("Http2Stream %p Tunnel Response code %d", this, code));
+    if ((code / 100) != 2) {
+      MapStreamToPlainText();
     }
   }
 
   if (aHeadersIn.Length() && aHeadersOut.Length()) {
     Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_SIZE, aHeadersIn.Length());
     uint32_t ratio =
       aHeadersIn.Length() * 100 / aHeadersOut.Length();
     Telemetry::Accumulate(Telemetry::SPDY_SYN_REPLY_RATIO, ratio);
   }
 
+  // The decoding went ok. Now we can customize and clean up.
+
   aHeadersIn.Truncate();
   aHeadersOut.Append("X-Firefox-Spdy: " NS_HTTP2_DRAFT_TOKEN "\r\n\r\n");
   LOG (("decoded response headers are:\n%s", aHeadersOut.BeginReading()));
-  if (mIsTunnel) {
+  if (mIsTunnel && !mPlainTextTunnel) {
     aHeadersOut.Truncate();
-    LOG(("SpdyStream3::ConvertHeaders %p 0x%X headers removed for tunnel\n",
+    LOG(("Http2Stream::ConvertHeaders %p 0x%X headers removed for tunnel\n",
          this, mStreamID));
   }
   return NS_OK;
 }
 
 // ConvertHeaders is used to convert the response headers
 // into HTTP/1 format and report some telemetry
 nsresult
@@ -1197,16 +1202,25 @@ Http2Stream::ClearTransactionsBlockedOnT
 
   if (!mIsTunnel) {
     return;
   }
   gHttpHandler->ConnMgr()->ProcessPendingQ(mTransaction->ConnectionInfo());
 }
 
 void
+Http2Stream::MapStreamToPlainText()
+{
+  nsRefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
+  MOZ_ASSERT(qiTrans);
+  mPlainTextTunnel = true;
+  qiTrans->ForcePlainText();
+}
+
+void
 Http2Stream::MapStreamToHttpConnection()
 {
   nsRefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
   MOZ_ASSERT(qiTrans);
   qiTrans->MapStreamToHttpConnection(mSocketTransport,
                                      mTransaction->ConnectionInfo());
 }
 
--- a/netwerk/protocol/http/Http2Stream.h
+++ b/netwerk/protocol/http/Http2Stream.h
@@ -270,17 +270,19 @@ private:
   // For Http2Push
   Http2PushedStream *mPushSource;
 
 /// connect tunnels
 public:
   bool IsTunnel() { return mIsTunnel; }
 private:
   void ClearTransactionsBlockedOnTunnel();
+  void MapStreamToPlainText();
   void MapStreamToHttpConnection();
 
   bool mIsTunnel;
+  bool mPlainTextTunnel;
 };
 
 } // namespace mozilla::net
 } // namespace mozilla
 
 #endif // mozilla_net_Http2Stream_h
--- a/netwerk/protocol/http/SpdySession3.cpp
+++ b/netwerk/protocol/http/SpdySession3.cpp
@@ -2516,35 +2516,40 @@ SpdySession3::DispatchOnTunnel(nsAHttpTr
 
   LOG3(("SpdySession3::DispatchOnTunnel %p trans=%p", this, trans));
 
   aHttpTransaction->SetConnection(nullptr);
 
   // this transaction has done its work of setting up a tunnel, let
   // the connection manager queue it if necessary
   trans->SetDontRouteViaWildCard(true);
+  trans->EnableKeepAlive();
 
   if (FindTunnelCount(ci) < gHttpHandler->MaxConnectionsPerOrigin()) {
     LOG3(("SpdySession3::DispatchOnTunnel %p create on new tunnel %s",
           this, ci->HashKey().get()));
+    // The connect transaction will hold onto the underlying http
+    // transaction so that an auth created by the connect can be mappped
+    // to the correct security callbacks
     nsRefPtr<SpdyConnectTransaction> connectTrans =
       new SpdyConnectTransaction(ci, aCallbacks,
                                  trans->Caps(), trans, this);
     AddStream(connectTrans, trans->Priority(),
               false, nullptr);
     SpdyStream3 *tunnel = mStreamTransactionHash.Get(connectTrans);
     MOZ_ASSERT(tunnel);
     RegisterTunnel(tunnel);
+  } else {
+    // requeue it. The connection manager is responsible for actually putting
+    // this on the tunnel connection with the specific ci now that it
+    // has DontRouteViaWildCard set.
+    LOG3(("SpdySession3::DispatchOnTunnel %p trans=%p queue in connection manager",
+          this, trans));
+    gHttpHandler->InitiateTransaction(trans, trans->Priority());
   }
-
-  // requeue it. The connection manager is responsible for actually putting
-  // this on the tunnel connection with the specific ci now that it
-  // has DontRouteViaWildCard set.
-  trans->EnableKeepAlive();
-  gHttpHandler->InitiateTransaction(trans, trans->Priority());
 }
 
 //-----------------------------------------------------------------------------
 // Modified methods of nsAHttpConnection
 //-----------------------------------------------------------------------------
 
 void
 SpdySession3::TransactionHasDataToWrite(nsAHttpTransaction *caller)
--- a/netwerk/protocol/http/SpdySession31.cpp
+++ b/netwerk/protocol/http/SpdySession31.cpp
@@ -2648,35 +2648,40 @@ SpdySession31::DispatchOnTunnel(nsAHttpT
 
   LOG3(("SpdySession31::DispatchOnTunnel %p trans=%p", this, trans));
 
   aHttpTransaction->SetConnection(nullptr);
 
   // this transaction has done its work of setting up a tunnel, let
   // the connection manager queue it if necessary
   trans->SetDontRouteViaWildCard(true);
+  trans->EnableKeepAlive();
 
   if (FindTunnelCount(ci) < gHttpHandler->MaxConnectionsPerOrigin()) {
     LOG3(("SpdySession31::DispatchOnTunnel %p create on new tunnel %s",
           this, ci->HashKey().get()));
+    // The connect transaction will hold onto the underlying http
+    // transaction so that an auth created by the connect can be mappped
+    // to the correct security callbacks
     nsRefPtr<SpdyConnectTransaction> connectTrans =
       new SpdyConnectTransaction(ci, aCallbacks,
                                  trans->Caps(), trans, this);
     AddStream(connectTrans, trans->Priority(),
               false, nullptr);
     SpdyStream31 *tunnel = mStreamTransactionHash.Get(connectTrans);
     MOZ_ASSERT(tunnel);
     RegisterTunnel(tunnel);
+  } else {
+    // requeue it. The connection manager is responsible for actually putting
+    // this on the tunnel connection with the specific ci now that it
+    // has DontRouteViaWildCard set.
+    LOG3(("SpdySession31::DispatchOnTunnel %p trans=%p queue in connection manager",
+          this, trans));
+    gHttpHandler->InitiateTransaction(trans, trans->Priority());
   }
-
-  // requeue it. The connection manager is responsible for actually putting
-  // this on the tunnel connection with the specific ci now that it
-  // has DontRouteViaWildCard set.
-  trans->EnableKeepAlive();
-  gHttpHandler->InitiateTransaction(trans, trans->Priority());
 }
 
 nsresult
 SpdySession31::BufferOutput(const char *buf,
                             uint32_t count,
                             uint32_t *countRead)
 {
   nsAHttpSegmentReader *old = mSegmentReader;
--- a/netwerk/protocol/http/SpdyStream3.cpp
+++ b/netwerk/protocol/http/SpdyStream3.cpp
@@ -67,16 +67,17 @@ SpdyStream3::SpdyStream3(nsAHttpTransact
   , mRequestBodyLenRemaining(0)
   , mPriority(priority)
   , mLocalUnacked(0)
   , mBlockedOnRwin(false)
   , mTotalSent(0)
   , mTotalRead(0)
   , mPushSource(nullptr)
   , mIsTunnel(false)
+  , mPlainTextTunnel(false)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
   LOG3(("SpdyStream3::SpdyStream3 %p", this));
 
   mRemoteWindow = spdySession->GetServerInitialWindow();
   mLocalWindow = spdySession->PushAllowance();
 
@@ -189,17 +190,17 @@ SpdyStream3::ReadSegments(nsAHttpSegment
     MOZ_ASSERT(false, "SpdyStream3::ReadSegments unknown state");
     break;
   }
 
   return rv;
 }
 
 // WriteSegments() is used to read data off the socket. Generally this is
-// just a call through to the associate nsHttpTransaciton for this stream
+// just a call through to the associated nsHttpTransaction for this stream
 // for the remaining data bytes indicated by the current DATA frame.
 
 nsresult
 SpdyStream3::WriteSegments(nsAHttpSegmentWriter *writer,
                           uint32_t count,
                           uint32_t *countWritten)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
@@ -1272,26 +1273,28 @@ SpdyStream3::ConvertHeaders(nsACString &
       nvpair += 8 + nameLen + valueLen;
     }
 
     // move to the next name/value header block (if there is one) - the
     // first pair is offset 4 bytes into it
     nvpair += 4;
   } while (lastHeaderByte >= nvpair);
 
+  // The decoding went ok. Now we can customize and clean up.
+
   aHeadersOut.AppendLiteral("X-Firefox-Spdy: 3\r\n\r\n");
   LOG (("decoded response headers are:\n%s",
         aHeadersOut.BeginReading()));
 
   // The spdy formatted buffer isnt needed anymore - free it up
   mDecompressBuffer = nullptr;
   mDecompressBufferSize = 0;
   mDecompressBufferUsed = 0;
 
-  if (mIsTunnel) {
+  if (mIsTunnel && !mPlainTextTunnel) {
     aHeadersOut.Truncate();
     LOG(("SpdyStream3::ConvertHeaders %p 0x%X headers removed for tunnel\n",
          this, mStreamID));
   }
 
   return NS_OK;
 }
 
@@ -1366,28 +1369,28 @@ SpdyStream3::GetFullyOpen()
 }
 
 nsresult
 SpdyStream3::SetFullyOpen()
 {
   MOZ_ASSERT(!mFullyOpen);
   mFullyOpen = 1;
   if (mIsTunnel) {
+    int32_t code = 0;
     nsDependentCSubstring statusSubstring;
-    nsresult rv = FindHeader(NS_LITERAL_CSTRING(":status"),
-                             statusSubstring);
+    nsresult rv = FindHeader(NS_LITERAL_CSTRING(":status"), statusSubstring);
     if (NS_SUCCEEDED(rv)) {
       nsCString status(statusSubstring);
       nsresult errcode;
+      code = status.ToInteger(&errcode);
+    }
 
-      if (status.ToInteger(&errcode) != 200) {
-        LOG3(("SpdyStream3::SetFullyOpen %p Tunnel not 200", this));
-        return NS_ERROR_FAILURE;
-      }
-      LOG3(("SpdyStream3::SetFullyOpen %p Tunnel 200 OK", this));
+    LOG3(("SpdyStream3::SetFullyOpen %p Tunnel Response code %d", this, code));
+    if ((code / 100) != 2) {
+      MapStreamToPlainText();
     }
 
     MapStreamToHttpConnection();
     ClearTransactionsBlockedOnTunnel();
   }
   return NS_OK;
 }
 
@@ -1557,16 +1560,25 @@ SpdyStream3::ClearTransactionsBlockedOnT
 
   if (!mIsTunnel) {
     return;
   }
   gHttpHandler->ConnMgr()->ProcessPendingQ(mTransaction->ConnectionInfo());
 }
 
 void
+SpdyStream3::MapStreamToPlainText()
+{
+  nsRefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
+  MOZ_ASSERT(qiTrans);
+  mPlainTextTunnel = true;
+  qiTrans->ForcePlainText();
+}
+
+void
 SpdyStream3::MapStreamToHttpConnection()
 {
   nsRefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
   MOZ_ASSERT(qiTrans);
   qiTrans->MapStreamToHttpConnection(mSocketTransport,
                                      mTransaction->ConnectionInfo());
 }
 
--- a/netwerk/protocol/http/SpdyStream3.h
+++ b/netwerk/protocol/http/SpdyStream3.h
@@ -252,16 +252,18 @@ private:
   // For SpdyPush
   SpdyPushedStream3 *mPushSource;
 
 /// connect tunnels
 public:
   bool IsTunnel() { return mIsTunnel; }
 private:
   void ClearTransactionsBlockedOnTunnel();
+  void MapStreamToPlainText();
   void MapStreamToHttpConnection();
 
   bool mIsTunnel;
+  bool mPlainTextTunnel;
 };
 
 }} // namespace mozilla::net
 
 #endif // mozilla_net_SpdyStream3_h
--- a/netwerk/protocol/http/SpdyStream31.cpp
+++ b/netwerk/protocol/http/SpdyStream31.cpp
@@ -65,16 +65,17 @@ SpdyStream31::SpdyStream31(nsAHttpTransa
   , mRequestBodyLenRemaining(0)
   , mPriority(priority)
   , mLocalUnacked(0)
   , mBlockedOnRwin(false)
   , mTotalSent(0)
   , mTotalRead(0)
   , mPushSource(nullptr)
   , mIsTunnel(false)
+  , mPlainTextTunnel(false)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
   LOG3(("SpdyStream31::SpdyStream31 %p", this));
 
   mRemoteWindow = spdySession->GetServerInitialStreamWindow();
   mLocalWindow = spdySession->PushAllowance();
 
@@ -194,17 +195,17 @@ SpdyStream31::ReadSegments(nsAHttpSegmen
     MOZ_ASSERT(false, "SpdyStream31::ReadSegments unknown state");
     break;
   }
 
   return rv;
 }
 
 // WriteSegments() is used to read data off the socket. Generally this is
-// just a call through to the associate nsHttpTransaciton for this stream
+// just a call through to the associated nsHttpTransaction for this stream
 // for the remaining data bytes indicated by the current DATA frame.
 
 nsresult
 SpdyStream31::WriteSegments(nsAHttpSegmentWriter *writer,
                             uint32_t count,
                             uint32_t *countWritten)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
@@ -1288,26 +1289,28 @@ SpdyStream31::ConvertHeaders(nsACString 
       nvpair += 8 + nameLen + valueLen;
     }
 
     // move to the next name/value header block (if there is one) - the
     // first pair is offset 4 bytes into it
     nvpair += 4;
   } while (lastHeaderByte >= nvpair);
 
+  // The decoding went ok. Now we can customize and clean up.
+
   aHeadersOut.AppendLiteral("X-Firefox-Spdy: 3.1\r\n\r\n");
   LOG (("decoded response headers are:\n%s",
         aHeadersOut.BeginReading()));
 
   // The spdy formatted buffer isnt needed anymore - free it up
   mDecompressBuffer = nullptr;
   mDecompressBufferSize = 0;
   mDecompressBufferUsed = 0;
 
-  if (mIsTunnel) {
+  if (mIsTunnel && !mPlainTextTunnel) {
     aHeadersOut.Truncate();
     LOG(("SpdyStream31::ConvertHeaders %p 0x%X headers removed for tunnel\n",
          this, mStreamID));
   }
 
   return NS_OK;
 }
 
@@ -1380,28 +1383,28 @@ SpdyStream31::GetFullyOpen()
 }
 
 nsresult
 SpdyStream31::SetFullyOpen()
 {
   MOZ_ASSERT(!mFullyOpen);
   mFullyOpen = 1;
   if (mIsTunnel) {
+    int32_t code = 0;
     nsDependentCSubstring statusSubstring;
-    nsresult rv = FindHeader(NS_LITERAL_CSTRING(":status"),
-                             statusSubstring);
+    nsresult rv = FindHeader(NS_LITERAL_CSTRING(":status"), statusSubstring);
     if (NS_SUCCEEDED(rv)) {
       nsCString status(statusSubstring);
       nsresult errcode;
+      code = status.ToInteger(&errcode);
+    }
 
-      if (status.ToInteger(&errcode) != 200) {
-        LOG3(("SpdyStream31::SetFullyOpen %p Tunnel not 200", this));
-        return NS_ERROR_FAILURE;
-      }
-      LOG3(("SpdyStream31::SetFullyOpen %p Tunnel 200 OK", this));
+    LOG3(("SpdyStream31::SetFullyOpen %p Tunnel Response code %d", this, code));
+    if ((code / 100) != 2) {
+      MapStreamToPlainText();
     }
 
     MapStreamToHttpConnection();
     ClearTransactionsBlockedOnTunnel();
   }
   return NS_OK;
 }
 
@@ -1581,16 +1584,25 @@ SpdyStream31::ClearTransactionsBlockedOn
 
   if (!mIsTunnel) {
     return;
   }
   gHttpHandler->ConnMgr()->ProcessPendingQ(mTransaction->ConnectionInfo());
 }
 
 void
+SpdyStream31::MapStreamToPlainText()
+{
+  nsRefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
+  MOZ_ASSERT(qiTrans);
+  mPlainTextTunnel = true;
+  qiTrans->ForcePlainText();
+}
+
+void
 SpdyStream31::MapStreamToHttpConnection()
 {
   nsRefPtr<SpdyConnectTransaction> qiTrans(mTransaction->QuerySpdyConnectTransaction());
   MOZ_ASSERT(qiTrans);
   qiTrans->MapStreamToHttpConnection(mSocketTransport,
                                      mTransaction->ConnectionInfo());
 }
 
--- a/netwerk/protocol/http/SpdyStream31.h
+++ b/netwerk/protocol/http/SpdyStream31.h
@@ -247,16 +247,18 @@ private:
   // For SpdyPush
   SpdyPushedStream31 *mPushSource;
 
 /// connect tunnels
 public:
   bool IsTunnel() { return mIsTunnel; }
 private:
   void ClearTransactionsBlockedOnTunnel();
+  void MapStreamToPlainText();
   void MapStreamToHttpConnection();
 
   bool mIsTunnel;
+  bool mPlainTextTunnel;
 };
 
 }} // namespace mozilla::net
 
 #endif // mozilla_net_SpdyStream31_h
--- a/netwerk/protocol/http/TunnelUtils.cpp
+++ b/netwerk/protocol/http/TunnelUtils.cpp
@@ -10,16 +10,17 @@
 #include "Http2Session.h"
 #include "nsHttp.h"
 #include "nsHttpHandler.h"
 #include "nsHttpRequestHead.h"
 #include "nsISocketProvider.h"
 #include "nsISocketProviderService.h"
 #include "nsISSLSocketControl.h"
 #include "nsISocketTransport.h"
+#include "nsISupportsPriority.h"
 #include "nsNetAddr.h"
 #include "prerror.h"
 #include "prio.h"
 #include "TunnelUtils.h"
 
 #ifdef DEBUG
 // defined by the socket transport service while active
 extern PRThread *gSocketThread;
@@ -805,42 +806,62 @@ private:
   nsWeakPtr mWeakTrans; // SpdyConnectTransaction *
   nsIInputStreamCallback *mCallback;
   nsresult mStatus;
 };
 
 SpdyConnectTransaction::SpdyConnectTransaction(nsHttpConnectionInfo *ci,
                                                nsIInterfaceRequestor *callbacks,
                                                uint32_t caps,
-                                               nsAHttpTransaction *trans,
+                                               nsHttpTransaction *trans,
                                                nsAHttpConnection *session)
   : NullHttpTransaction(ci, callbacks, caps | NS_HTTP_ALLOW_KEEPALIVE)
   , mConnectStringOffset(0)
   , mSession(session)
   , mSegmentReader(nullptr)
   , mInputDataSize(0)
   , mInputDataUsed(0)
   , mInputDataOffset(0)
   , mOutputDataSize(0)
   , mOutputDataUsed(0)
   , mOutputDataOffset(0)
+  , mForcePlainText(false)
 {
   LOG(("SpdyConnectTransaction ctor %p\n", this));
 
   mTimestampSyn = TimeStamp::Now();
   mRequestHead = new nsHttpRequestHead();
   nsHttpConnection::MakeConnectString(trans, mRequestHead, mConnectString);
+  mDrivingTransaction = trans;
 }
 
 SpdyConnectTransaction::~SpdyConnectTransaction()
 {
   LOG(("SpdyConnectTransaction dtor %p\n", this));
   if (mRequestHead) {
     delete mRequestHead;
   }
+
+  if (mDrivingTransaction) {
+    // requeue it I guess. This should be gone.
+    gHttpHandler->InitiateTransaction(mDrivingTransaction,
+                                      mDrivingTransaction->Priority());
+  }
+}
+
+void
+SpdyConnectTransaction::ForcePlainText()
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+  MOZ_ASSERT(!mInputDataUsed && !mInputDataSize && !mInputDataOffset);
+  MOZ_ASSERT(!mForcePlainText);
+  MOZ_ASSERT(!mTunnelTransport, "call before mapstreamtohttpconnection");
+
+  mForcePlainText = true;
+  return;
 }
 
 void
 SpdyConnectTransaction::MapStreamToHttpConnection(nsISocketTransport *aTransport,
                                                   nsHttpConnectionInfo *aConnInfo)
 {
   mConnInfo = aConnInfo;
 
@@ -860,20 +881,33 @@ SpdyConnectTransaction::MapStreamToHttpC
   MOZ_ASSERT(aConnInfo->UsingHttpsProxy());
   TimeDuration rtt = TimeStamp::Now() - mTimestampSyn;
   mTunneledConn->Init(aConnInfo,
                       gHttpHandler->ConnMgr()->MaxRequestDelay(),
                       mTunnelTransport, mTunnelStreamIn, mTunnelStreamOut,
                       true, callbacks,
                       PR_MillisecondsToInterval(
                         static_cast<uint32_t>(rtt.ToMilliseconds())));
-  mTunneledConn->SetupSecondaryTLS();
-  mTunneledConn->SetInSpdyTunnel(true);
+  if (mForcePlainText) {
+      mTunneledConn->ForcePlainText();
+  } else {
+    mTunneledConn->SetupSecondaryTLS();
+    mTunneledConn->SetInSpdyTunnel(true);
+  }
 
-  gHttpHandler->ConnMgr()->ReclaimConnection(mTunneledConn);
+  // make the originating transaction stick to the tunneled conn
+  nsRefPtr<nsAHttpConnection> wrappedConn =
+    gHttpHandler->ConnMgr()->MakeConnectionHandle(mTunneledConn);
+  mDrivingTransaction->SetConnection(wrappedConn);
+  mDrivingTransaction->MakeSticky();
+
+  // jump the priority and start the dispatcher
+  gHttpHandler->InitiateTransaction(
+    mDrivingTransaction, nsISupportsPriority::PRIORITY_HIGHEST - 60);
+  mDrivingTransaction = nullptr;
 }
 
 nsresult
 SpdyConnectTransaction::Flush(uint32_t count, uint32_t *countRead)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
   LOG(("SpdyConnectTransaction::Flush %p count %d avail %d\n",
        this, count, mOutputDataUsed - mOutputDataOffset));
@@ -906,17 +940,18 @@ SpdyConnectTransaction::Flush(uint32_t c
 }
 
 nsresult
 SpdyConnectTransaction::ReadSegments(nsAHttpSegmentReader *reader,
                                      uint32_t count,
                                      uint32_t *countRead)
 {
   MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
-  LOG(("SpdyConnectTransaction::ReadSegments %p count %d\n", this, count));
+  LOG(("SpdyConnectTransaction::ReadSegments %p count %d conn %p\n",
+       this, count, mTunneledConn.get()));
 
   mSegmentReader = reader;
 
   // spdy stream carrying tunnel is not setup yet.
   if (!mTunneledConn) {
     uint32_t toWrite = mConnectString.Length() - mConnectStringOffset;
     toWrite = std::min(toWrite, count);
     *countRead = toWrite;
@@ -935,16 +970,27 @@ SpdyConnectTransaction::ReadSegments(nsA
           mConnectStringOffset = 0;
         }
       }
       return rv;
     }
     return NS_BASE_STREAM_WOULD_BLOCK;
   }
 
+  if (mForcePlainText) {
+    // this path just ignores sending the request so that we can
+    // send a synthetic reply in writesegments()
+    LOG(("SpdyConnectTransaciton::ReadSegments %p dropping %d output bytes "
+         "due to synthetic reply\n", this, mOutputDataUsed - mOutputDataOffset));
+    *countRead = mOutputDataUsed - mOutputDataOffset;
+    mOutputDataOffset = mOutputDataUsed = 0;
+    mTunneledConn->DontReuse();
+    return NS_OK;
+  }
+
   *countRead = 0;
   Flush(count, countRead);
   if (!mTunnelStreamOut->mCallback) {
     return NS_BASE_STREAM_WOULD_BLOCK;
   }
 
   nsresult rv =
     mTunnelStreamOut->mCallback->OnOutputStreamReady(mTunnelStreamOut);
@@ -993,17 +1039,17 @@ SpdyConnectTransaction::WriteSegments(ns
 
   // first call into the tunnel stream to get the demux'd data out of the
   // spdy session.
   EnsureBuffer(mInputData, mInputDataUsed + count, mInputDataUsed, mInputDataSize);
   nsresult rv = writer->OnWriteSegment(mInputData + mInputDataUsed,
                                        count, countWritten);
   if (NS_FAILED(rv)) {
     if (rv != NS_BASE_STREAM_WOULD_BLOCK) {
-      LOG(("SpdyConnectTransaction::Flush %p Error %x\n", this, rv));
+      LOG(("SpdyConnectTransaction::WriteSegments wrapped writer %p Error %x\n", this, rv));
       CreateShimError(rv);
     }
     return rv;
   }
   mInputDataUsed += *countWritten;
   LOG(("SpdyConnectTransaction %p %d new bytes [%d total] of ciphered data buffered\n",
        this, *countWritten, mInputDataUsed - mInputDataOffset));
 
--- a/netwerk/protocol/http/TunnelUtils.h
+++ b/netwerk/protocol/http/TunnelUtils.h
@@ -170,22 +170,25 @@ class nsHttpConnection;
 class ASpdySession;
 
 class SpdyConnectTransaction MOZ_FINAL : public NullHttpTransaction
 {
 public:
   SpdyConnectTransaction(nsHttpConnectionInfo *ci,
                          nsIInterfaceRequestor *callbacks,
                          uint32_t caps,
-                         nsAHttpTransaction *trans,
+                         nsHttpTransaction *trans,
                          nsAHttpConnection *session);
   ~SpdyConnectTransaction();
 
   SpdyConnectTransaction *QuerySpdyConnectTransaction() { return this; }
 
+  // A transaction is forced into plaintext when it is intended to be used as a CONNECT
+  // tunnel but the setup fails. The plaintext only carries the CONNECT error.
+  void ForcePlainText();
   void MapStreamToHttpConnection(nsISocketTransport *aTransport,
                                  nsHttpConnectionInfo *aConnInfo);
 
   nsresult ReadSegments(nsAHttpSegmentReader *reader,
                         uint32_t count, uint32_t *countRead) MOZ_OVERRIDE MOZ_FINAL;
   nsresult WriteSegments(nsAHttpSegmentWriter *writer,
                          uint32_t count, uint32_t *countWritten) MOZ_OVERRIDE MOZ_FINAL;
   nsHttpRequestHead *RequestHead() MOZ_OVERRIDE MOZ_FINAL;
@@ -210,24 +213,26 @@ private:
   uint32_t             mInputDataUsed;
   uint32_t             mInputDataOffset;
 
   nsAutoArrayPtr<char> mOutputData;
   uint32_t             mOutputDataSize;
   uint32_t             mOutputDataUsed;
   uint32_t             mOutputDataOffset;
 
+  bool                           mForcePlainText;
   TimeStamp                      mTimestampSyn;
   nsRefPtr<nsHttpConnectionInfo> mConnInfo;
 
   // mTunneledConn, mTunnelTransport, mTunnelStreamIn, mTunnelStreamOut
   // are the connectors to the "real" http connection. They are created
   // together when the tunnel setup is complete and a static reference is held
   // for the lifetime of the tunnel.
   nsRefPtr<nsHttpConnection>     mTunneledConn;
   nsRefPtr<SocketTransportShim>  mTunnelTransport;
   nsRefPtr<InputStreamShim>      mTunnelStreamIn;
   nsRefPtr<OutputStreamShim>     mTunnelStreamOut;
+  nsRefPtr<nsHttpTransaction>    mDrivingTransaction;
 };
 
 }} // namespace mozilla::net
 
 #endif // mozilla_net_TLSFilterTransaction_h
--- a/netwerk/protocol/http/nsHttpChannelAuthProvider.h
+++ b/netwerk/protocol/http/nsHttpChannelAuthProvider.h
@@ -42,17 +42,17 @@ private:
     int32_t     ProxyPort() const
     { return mProxyInfo ? mProxyInfo->Port() : -1; }
 
     const char *Host() const      { return mHost.get(); }
     int32_t     Port() const      { return mPort; }
     bool        UsingSSL() const  { return mUsingSSL; }
 
     bool        UsingHttpProxy() const
-    { return !!(mProxyInfo && !nsCRT::strcmp(mProxyInfo->Type(), "http")); }
+    { return mProxyInfo && (mProxyInfo->IsHTTP() || mProxyInfo->IsHTTPS()); }
 
     nsresult PrepareForAuthentication(bool proxyAuth);
     nsresult GenCredsAndSetEntry(nsIHttpAuthenticator *, bool proxyAuth,
                                  const char *scheme, const char *host,
                                  int32_t port, const char *dir,
                                  const char *realm, const char *challenge,
                                  const nsHttpAuthIdentity &ident,
                                  nsCOMPtr<nsISupports> &session, char **result);
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -63,16 +63,17 @@ nsHttpConnection::nsHttpConnection()
     , mSupportsPipelining(false) // assume low-grade server
     , mIsReused(false)
     , mCompletedProxyConnect(false)
     , mLastTransactionExpectedNoContent(false)
     , mIdleMonitoring(false)
     , mProxyConnectInProgress(false)
     , mExperienced(false)
     , mInSpdyTunnel(false)
+    , mForcePlainText(false)
     , mHttp1xTransactionCount(0)
     , mRemainingConnectionUses(0xffffffff)
     , mClassification(nsAHttpTransaction::CLASS_GENERAL)
     , mNPNComplete(false)
     , mSetupSSLCalled(false)
     , mUsingSpdyVersion(0)
     , mPriority(nsISupportsPriority::PRIORITY_NORMAL)
     , mReportedSpdy(false)
@@ -455,17 +456,17 @@ nsHttpConnection::SetupSSL()
 
     if (mNPNComplete)
         return;
 
     // we flip this back to false if SetNPNList succeeds at the end
     // of this function
     mNPNComplete = true;
 
-    if (!mConnInfo->FirstHopSSL()) {
+    if (!mConnInfo->FirstHopSSL() || mForcePlainText) {
         return;
     }
 
     // if we are connected to the proxy with TLS, start the TLS
     // flow immediately without waiting for a CONNECT sequence.
     if (mInSpdyTunnel) {
         InitSSLParams(false, true);
     } else {
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -105,29 +105,36 @@ public:
         mLastTransactionExpectedNoContent = val;
     }
 
     bool NeedSpdyTunnel()
     {
         return mConnInfo->UsingHttpsProxy() && !mTLSFilter && mConnInfo->UsingConnect();
     }
 
+    // A connection is forced into plaintext when it is intended to be used as a CONNECT
+    // tunnel but the setup fails. The plaintext only carries the CONNECT error.
+    void ForcePlainText()
+    {
+        mForcePlainText = true;
+    }
+
     nsISocketTransport   *Transport()      { return mSocketTransport; }
     nsAHttpTransaction   *Transaction()    { return mTransaction; }
     nsHttpConnectionInfo *ConnectionInfo() { return mConnInfo; }
 
     // nsAHttpConnection compatible methods (non-virtual):
     nsresult OnHeadersAvailable(nsAHttpTransaction *, nsHttpRequestHead *, nsHttpResponseHead *, bool *reset);
     void     CloseTransaction(nsAHttpTransaction *, nsresult reason);
     void     GetConnectionInfo(nsHttpConnectionInfo **ci) { NS_IF_ADDREF(*ci = mConnInfo); }
     nsresult TakeTransport(nsISocketTransport **,
                            nsIAsyncInputStream **,
                            nsIAsyncOutputStream **);
     void     GetSecurityInfo(nsISupports **);
-    bool     IsPersistent() { return IsKeepAlive(); }
+    bool     IsPersistent() { return IsKeepAlive() && !mDontReuse; }
     bool     IsReused();
     void     SetIsReusedAfter(uint32_t afterMilliseconds);
     nsresult PushBack(const char *data, uint32_t length);
     nsresult ResumeSend();
     nsresult ResumeRecv();
     int64_t  MaxBytesRead() {return mMaxBytesRead;}
     uint8_t GetLastHttpResponseVersion() { return mLastHttpResponseVersion; }
 
@@ -277,16 +284,17 @@ private:
     bool                            mSupportsPipelining;
     bool                            mIsReused;
     bool                            mCompletedProxyConnect;
     bool                            mLastTransactionExpectedNoContent;
     bool                            mIdleMonitoring;
     bool                            mProxyConnectInProgress;
     bool                            mExperienced;
     bool                            mInSpdyTunnel;
+    bool                            mForcePlainText;
 
     // The number of <= HTTP/1.1 transactions performed on this connection. This
     // excludes spdy transactions.
     uint32_t                        mHttp1xTransactionCount;
 
     // Keep-Alive: max="mRemainingConnectionUses" provides the number of future
     // transactions (including the current one) that the server expects to allow
     // on this persistent connection.
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -1974,20 +1974,31 @@ nsHttpConnectionMgr::ProcessNewTransacti
 
     nsAHttpConnection *wrappedConnection = trans->Connection();
     nsRefPtr<nsHttpConnection> conn;
     if (wrappedConnection)
         conn = dont_AddRef(wrappedConnection->TakeHttpConnection());
 
     if (conn) {
         MOZ_ASSERT(trans->Caps() & NS_HTTP_STICKY_CONNECTION);
-        MOZ_ASSERT(((int32_t)ent->mActiveConns.IndexOf(conn)) != -1,
-                   "Sticky Connection Not In Active List");
         LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
              "sticky connection=%p\n", trans, conn.get()));
+
+        if (static_cast<int32_t>(ent->mActiveConns.IndexOf(conn)) == -1) {
+            LOG(("nsHttpConnectionMgr::ProcessNewTransaction trans=%p "
+                 "sticky connection=%p needs to go on the active list\n", trans, conn.get()));
+
+            // make sure it isn't on the idle list - we expect this to be an
+            // unknown fresh connection
+            MOZ_ASSERT(static_cast<int32_t>(ent->mIdleConns.IndexOf(conn)) == -1);
+            MOZ_ASSERT(!conn->IsExperienced());
+
+            AddActiveConn(conn, ent); // make it active
+        }
+
         trans->SetConnection(nullptr);
         rv = DispatchTransaction(ent, trans, conn);
     } else {
         rv = TryDispatchTransaction(ent, trans->DontRouteViaWildCard(), trans);
     }
 
     if (NS_SUCCEEDED(rv)) {
         LOG(("  ProcessNewTransaction Dispatch Immediately trans=%p\n", trans));
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -399,16 +399,22 @@ private:
         NS_DECL_THREADSAFE_ISUPPORTS
         NS_DECL_NSAHTTPCONNECTION(mConn)
 
         nsConnectionHandle(nsHttpConnection *conn) { NS_ADDREF(mConn = conn); }
         virtual ~nsConnectionHandle();
 
         nsHttpConnection *mConn;
     };
+public:
+    static nsAHttpConnection *MakeConnectionHandle(nsHttpConnection *aWrapped)
+    {
+        return new nsConnectionHandle(aWrapped);
+    }
+private:
 
     // nsHalfOpenSocket is used to hold the state of an opening TCP socket
     // while we wait for it to establish and bind it to a connection
 
     class nsHalfOpenSocket MOZ_FINAL : public nsIOutputStreamCallback,
                                        public nsITransportEventSink,
                                        public nsIInterfaceRequestor,
                                        public nsITimerCallback
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -105,16 +105,17 @@ public:
     // setting mDontRouteViaWildCard to true means the transaction should only
     // be dispatched on a specific ConnectionInfo Hash Key (as opposed to a
     // generic wild card one). That means in the specific case of carrying this
     // transaction on an HTTP/2 tunnel it will only be dispatched onto an
     // existing tunnel instead of triggering creation of a new one.
     void SetDontRouteViaWildCard(bool var) { mDontRouteViaWildCard = var; }
     bool DontRouteViaWildCard() { return mDontRouteViaWildCard; }
     void EnableKeepAlive() { mCaps |= NS_HTTP_ALLOW_KEEPALIVE; }
+    void MakeSticky() { mCaps |= NS_HTTP_STICKY_CONNECTION; }
 
     // SetPriority() may only be used by the connection manager.
     void    SetPriority(int32_t priority) { mPriority = priority; }
     int32_t    Priority()                 { return mPriority; }
 
     const TimingStruct& Timings() const { return mTimings; }
     enum Classifier Classification() { return mClassification; }