bug 597684 Implement HTTP Assoc-req and Banned Pipelines on nsHttpConnectionInfo r=honzab
authorPatrick McManus <mcmanus@ducksong.com>
Thu, 22 Mar 2012 19:39:31 -0400
changeset 91909 fd6f1cca200c3b750198ec03cbd82e8fa44b59ec
parent 91908 3dd62d76cc6d73fcfc47f6d5677099cf6aad4c1c
child 91910 ee6328d11bfbdb4522005db634ff4647768520d6
push idunknown
push userunknown
push dateunknown
reviewershonzab
bugs597684
milestone14.0a1
bug 597684 Implement HTTP Assoc-req and Banned Pipelines on nsHttpConnectionInfo r=honzab
modules/libpref/src/init/all.js
netwerk/protocol/http/SpdySession.cpp
netwerk/protocol/http/nsAHttpTransaction.h
netwerk/protocol/http/nsHttpAtomList.h
netwerk/protocol/http/nsHttpChannel.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/protocol/http/nsHttpConnection.cpp
netwerk/protocol/http/nsHttpConnectionInfo.cpp
netwerk/protocol/http/nsHttpConnectionInfo.h
netwerk/protocol/http/nsHttpHandler.cpp
netwerk/protocol/http/nsHttpHandler.h
netwerk/protocol/http/nsHttpPipeline.cpp
netwerk/protocol/http/nsHttpTransaction.cpp
netwerk/protocol/http/nsHttpTransaction.h
netwerk/test/unit/head_channels.js
netwerk/test/unit/test_assoc.js
netwerk/test/unit/xpcshell.ini
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -809,16 +809,20 @@ pref("network.http.pipelining.ssl"  , fa
 pref("network.http.proxy.pipelining", false);
 
 // Max number of requests in the pipeline
 pref("network.http.pipelining.maxrequests" , 4);
 
 // Prompt for 307 redirects
 pref("network.http.prompt-temp-redirect", true);
 
+// If true generate CORRUPTED_CONTENT errors for entities that
+// contain an invalid Assoc-Req response header
+pref("network.http.assoc-req.enforce", false);
+
 // On networks deploying QoS, it is recommended that these be lockpref()'d,
 // since inappropriate marking can easily overwhelm bandwidth reservations
 // for certain services (i.e. EF for VoIP, AF4x for interactive video,
 // AF3x for broadcast/streaming video, etc)
 
 // default value for HTTP
 // in a DSCP environment this should be 40 (0x28, or AF11), per RFC-4594,
 // Section 4.8 "High-Throughput Data Service Class"
--- a/netwerk/protocol/http/SpdySession.cpp
+++ b/netwerk/protocol/http/SpdySession.cpp
@@ -2154,16 +2154,34 @@ SpdySession::AddTransaction(nsAHttpTrans
 PRUint16
 SpdySession::PipelineDepthAvailable()
 {
   // any attempt at pipelining will be turned into parallelism
 
   return 0;
 }
 
+nsresult
+SpdySession::SetPipelinePosition(PRInt32 position)
+{
+  // This API is meant for pipelining, SpdySession's should be
+  // extended with AddStream()
+
+  NS_ABORT_IF_FALSE(false,
+                    "SpdySession::SetPipelinePosition() should not be called");
+
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+PRInt32
+SpdySession::PipelinePosition()
+{
+    return 0;
+}
+
 //-----------------------------------------------------------------------------
 // Pass through methods of nsAHttpConnection
 //-----------------------------------------------------------------------------
 
 nsAHttpConnection *
 SpdySession::Connection()
 {
   NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
--- a/netwerk/protocol/http/nsAHttpTransaction.h
+++ b/netwerk/protocol/http/nsAHttpTransaction.h
@@ -118,16 +118,22 @@ public:
 
     // called to add a sub-transaction in the case of pipelined transactions
     // classes that do not implement sub transactions
     // return NS_ERROR_NOT_IMPLEMENTED
     virtual nsresult AddTransaction(nsAHttpTransaction *transaction) = 0;
     
     // called to count the number of sub transactions that can be added
     virtual PRUint16 PipelineDepthAvailable() = 0;
+
+    // Used to inform the connection that it is being used in a pipelined
+    // context. That may influence the handling of some errors.
+    // The value is the pipeline position.
+    virtual nsresult SetPipelinePosition(PRInt32) = 0;
+    virtual PRInt32  PipelinePosition() = 0;
 };
 
 #define NS_DECL_NSAHTTPTRANSACTION \
     void SetConnection(nsAHttpConnection *); \
     nsAHttpConnection *Connection(); \
     void GetSecurityCallbacks(nsIInterfaceRequestor **, \
                               nsIEventTarget **);       \
     void OnTransportStatus(nsITransport* transport, \
@@ -139,17 +145,19 @@ public:
     nsresult ReadSegments(nsAHttpSegmentReader *, PRUint32, PRUint32 *); \
     nsresult WriteSegments(nsAHttpSegmentWriter *, PRUint32, PRUint32 *); \
     void     Close(nsresult reason);                                    \
     void     SetSSLConnectFailed();                                     \
     nsHttpRequestHead *RequestHead();                                   \
     PRUint32 Http1xTransactionCount();                                  \
     nsresult TakeSubTransactions(nsTArray<nsRefPtr<nsAHttpTransaction> > &outTransactions); \
     nsresult AddTransaction(nsAHttpTransaction *);                      \
-    PRUint16 PipelineDepthAvailable();
+    PRUint16 PipelineDepthAvailable();                                  \
+    nsresult SetPipelinePosition(PRInt32);                              \
+    PRInt32  PipelinePosition();
 
 //-----------------------------------------------------------------------------
 // nsAHttpSegmentReader
 //-----------------------------------------------------------------------------
 
 class nsAHttpSegmentReader
 {
 public:
--- a/netwerk/protocol/http/nsHttpAtomList.h
+++ b/netwerk/protocol/http/nsHttpAtomList.h
@@ -52,16 +52,17 @@
 
 HTTP_ATOM(Accept,                    "Accept")
 HTTP_ATOM(Accept_Encoding,           "Accept-Encoding")
 HTTP_ATOM(Accept_Language,           "Accept-Language")
 HTTP_ATOM(Accept_Ranges,             "Accept-Ranges")
 HTTP_ATOM(Age,                       "Age")
 HTTP_ATOM(Allow,                     "Allow")
 HTTP_ATOM(Alternate_Protocol,        "Alternate-Protocol")
+HTTP_ATOM(Assoc_Req,                 "Assoc-Req")
 HTTP_ATOM(Authentication,            "Authentication")
 HTTP_ATOM(Authorization,             "Authorization")
 HTTP_ATOM(Cache_Control,             "Cache-Control")
 HTTP_ATOM(Connection,                "Connection")
 HTTP_ATOM(Content_Disposition,       "Content-Disposition")
 HTTP_ATOM(Content_Encoding,          "Content-Encoding")
 HTTP_ATOM(Content_Language,          "Content-Language")
 HTTP_ATOM(Content_Length,            "Content-Length")
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -42,16 +42,17 @@
  * 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 ***** */
 
 #include "nsHttpChannel.h"
 #include "nsHttpHandler.h"
+#include "nsStandardURL.h"
 #include "nsIApplicationCacheService.h"
 #include "nsIApplicationCacheContainer.h"
 #include "nsIAuthInformation.h"
 #include "nsIStringBundle.h"
 #include "nsIIDNService.h"
 #include "nsIStreamListenerTee.h"
 #include "nsISeekableStream.h"
 #include "nsMimeTypes.h"
@@ -65,16 +66,17 @@
 #include "nsDNSPrefetch.h"
 #include "nsChannelClassifier.h"
 #include "nsIRedirectResultListener.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Telemetry.h"
 #include "nsDOMError.h"
 #include "nsAlgorithm.h"
 #include "sampler.h"
+#include "nsIConsoleService.h"
 
 using namespace mozilla;
 
 // Device IDs for various cache types
 const char kDiskDeviceID[] = "disk";
 const char kMemoryDeviceID[] = "memory";
 const char kOfflineDeviceID[] = "offline";
 
@@ -768,16 +770,20 @@ nsHttpChannel::CallOnStartRequest()
     LOG(("  calling mListener->OnStartRequest\n"));
     nsresult rv = mListener->OnStartRequest(this, mListenerContext);
     if (NS_FAILED(rv)) return rv;
 
     // install stream converter if required
     rv = ApplyContentConversions();
     if (NS_FAILED(rv)) return rv;
 
+    rv = EnsureAssocReq();
+    if (NS_FAILED(rv))
+        return rv;
+
     // if this channel is for a download, close off access to the cache.
     if (mCacheEntry && mChannelIsForDownload) {
         mCacheEntry->Doom();
         CloseCacheEntry(false);
     }
 
     if (!mCanceled) {
         // create offline cache entry if offline caching was requested
@@ -1713,16 +1719,121 @@ nsHttpChannel::Hash(const char *buf, nsA
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = mHasher->Finish(true, hash);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
 }
 
+nsresult
+nsHttpChannel::EnsureAssocReq()
+{
+    // Confirm Assoc-Req response header on pipelined transactions
+    // per draft-nottingham-http-pipeline-01.txt
+    // of the form: GET http://blah.com/foo/bar?qv
+    // return NS_OK as long as we don't find a violation
+    // (i.e. no header is ok, as are malformed headers, as are
+    // transactions that have not been pipelined (unless those have been
+    // opted in via pragma))
+
+    if (!mResponseHead)
+        return NS_OK;
+
+    const char *assoc_val = mResponseHead->PeekHeader(nsHttp::Assoc_Req);
+    if (!assoc_val)
+        return NS_OK;
+
+    if (!mTransaction || !mURI)
+        return NS_OK;
+    
+    if (!mTransaction->PipelinePosition()) {
+        // "Pragma: X-Verify-Assoc-Req" can be used to verify even non pipelined
+        // transactions. It is used by test harness.
+
+        const char *pragma_val = mResponseHead->PeekHeader(nsHttp::Pragma);
+        if (!pragma_val ||
+            !nsHttp::FindToken(pragma_val, "X-Verify-Assoc-Req",
+                               HTTP_HEADER_VALUE_SEPS))
+            return NS_OK;
+    }
+
+    char *method = net_FindCharNotInSet(assoc_val, HTTP_LWS);
+    if (!method)
+        return NS_OK;
+    
+    bool equals;
+    char *endofmethod;
+    
+    assoc_val = nsnull;
+    endofmethod = net_FindCharInSet(method, HTTP_LWS);
+    if (endofmethod)
+        assoc_val = net_FindCharNotInSet(endofmethod, HTTP_LWS);
+    if (!assoc_val)
+        return NS_OK;
+    
+    // check the method
+    PRInt32 methodlen = PL_strlen(mRequestHead.Method().get());
+    if ((methodlen != (endofmethod - method)) ||
+        PL_strncmp(method,
+                   mRequestHead.Method().get(),
+                   endofmethod - method)) {
+        LOG(("  Assoc-Req failure Method %s", method));
+        if (mConnectionInfo)
+            mConnectionInfo->BanPipelining();
+
+        nsCOMPtr<nsIConsoleService> consoleService =
+            do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+        if (consoleService) {
+            nsAutoString message
+                (NS_LITERAL_STRING("Failed Assoc-Req. Received "));
+            AppendASCIItoUTF16(
+                mResponseHead->PeekHeader(nsHttp::Assoc_Req),
+                message);
+            message += NS_LITERAL_STRING(" expected method ");
+            AppendASCIItoUTF16(mRequestHead.Method().get(), message);
+            consoleService->LogStringMessage(message.get());
+        }
+
+        if (gHttpHandler->EnforceAssocReq())
+            return NS_ERROR_CORRUPTED_CONTENT;
+        return NS_OK;
+    }
+    
+    // check the URL
+    nsCOMPtr<nsIURI> assoc_url;
+    if (NS_FAILED(NS_NewURI(getter_AddRefs(assoc_url), assoc_val)) ||
+        !assoc_url)
+        return NS_OK;
+
+    mURI->Equals(assoc_url, &equals);
+    if (!equals) {
+        LOG(("  Assoc-Req failure URL %s", assoc_val));
+        if (mConnectionInfo)
+            mConnectionInfo->BanPipelining();
+
+        nsCOMPtr<nsIConsoleService> consoleService =
+            do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+        if (consoleService) {
+            nsAutoString message
+                (NS_LITERAL_STRING("Failed Assoc-Req. Received "));
+            AppendASCIItoUTF16(
+                mResponseHead->PeekHeader(nsHttp::Assoc_Req),
+                message);
+            message += NS_LITERAL_STRING(" expected URL ");
+            AppendASCIItoUTF16(mSpec.get(), message);
+            consoleService->LogStringMessage(message.get());
+        }
+
+        if (gHttpHandler->EnforceAssocReq())
+            return NS_ERROR_CORRUPTED_CONTENT;
+    }
+    return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpChannel <byte-range>
 //-----------------------------------------------------------------------------
 
 nsresult
 nsHttpChannel::SetupByteRangeRequest(PRUint32 partialLen)
 {
     // cached content has been found to be partial, add necessary request
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -177,16 +177,17 @@ private:
     nsresult ContinueProcessRedirection(nsresult);
     nsresult ContinueProcessRedirectionAfterFallback(nsresult);
     bool     ShouldSSLProxyResponseContinue(PRUint32 httpStatus);
     nsresult ProcessFailedSSLConnect(PRUint32 httpStatus);
     nsresult ProcessFallback(bool *waitingForRedirectCallback);
     nsresult ContinueProcessFallback(nsresult);
     bool     ResponseWouldVary();
     void     HandleAsyncAbort();
+    nsresult EnsureAssocReq();
 
     nsresult ContinueOnStartRequest1(nsresult);
     nsresult ContinueOnStartRequest2(nsresult);
     nsresult ContinueOnStartRequest3(nsresult);
 
     // redirection specific methods
     void     HandleAsyncRedirect();
     nsresult ContinueHandleAsyncRedirect(nsresult);
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -735,18 +735,23 @@ nsHttpConnection::OnHeadersAvailable(nsA
             // determination must be based on comunication with the
             // target server in this case. See bug 422016 for futher
             // details.
             if (!mProxyConnectStream)
               mSupportsPipelining = SupportsPipelining(responseHead);
         }
     }
     mKeepAliveMask = mKeepAlive;
-    mConnInfo->SetSupportsPipelining(mSupportsPipelining);
 
+    // Update the pipelining status in the connection info object
+    // and also read it back. It is possible the ci status is
+    // locked to false if pipelining has been banned on this ci due to
+    // some kind of observed flaky behavior
+    mSupportsPipelining = mConnInfo->SetSupportsPipelining(mSupportsPipelining);
+    
     // if this connection is persistent, then the server may send a "Keep-Alive"
     // header specifying the maximum number of times the connection can be
     // reused as well as the maximum amount of time the connection can be idle
     // before the server will close it.  we ignore the max reuse count, because
     // a "keep-alive" connection is by definition capable of being reused, and
     // we only care about being able to reuse it once.  if a timeout is not 
     // specified then we use our advertized timeout value.
     if (mKeepAlive) {
--- a/netwerk/protocol/http/nsHttpConnectionInfo.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.cpp
@@ -96,16 +96,37 @@ nsHttpConnectionInfo::Clone() const
 
     // Make sure the anonymous flag is transferred!
     clone->SetAnonymous(mHashKey.CharAt(2) == 'A');
     
     return clone;
 }
 
 bool
+nsHttpConnectionInfo::SupportsPipelining()
+{
+    return mSupportsPipelining;
+}
+
+bool
+nsHttpConnectionInfo::SetSupportsPipelining(bool support)
+{
+    if (!mBannedPipelining)
+        mSupportsPipelining = support;
+    return mSupportsPipelining;
+}
+
+void
+nsHttpConnectionInfo::BanPipelining()
+{
+    mBannedPipelining = true;
+    mSupportsPipelining = false;
+}
+
+bool
 nsHttpConnectionInfo::ShouldForceConnectMethod()
 {
     if (!mProxyInfo)
         return false;
     
     PRUint32 resolveFlags;
     nsresult rv;
     
--- a/netwerk/protocol/http/nsHttpConnectionInfo.h
+++ b/netwerk/protocol/http/nsHttpConnectionInfo.h
@@ -56,16 +56,17 @@ class nsHttpConnectionInfo
 public:
     nsHttpConnectionInfo(const nsACString &host, PRInt32 port,
                          nsProxyInfo* proxyInfo,
                          bool usingSSL=false)
         : mRef(0)
         , mProxyInfo(proxyInfo)
         , mUsingSSL(usingSSL)
         , mSupportsPipelining(false)
+        , mBannedPipelining(false)
     {
         LOG(("Creating nsHttpConnectionInfo @%x\n", this));
 
         mUsingHttpProxy = (proxyInfo && !nsCRT::strcmp(proxyInfo->Type(), "http"));
 
         SetOriginServer(host, port);
     }
     
@@ -122,27 +123,28 @@ public:
     nsProxyInfo  *ProxyInfo()            { return mProxyInfo; }
     bool          UsingHttpProxy() const { return mUsingHttpProxy; }
     bool          UsingSSL() const       { return mUsingSSL; }
     PRInt32       DefaultPort() const    { return mUsingSSL ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT; }
     void          SetAnonymous(bool anon)         
                                          { mHashKey.SetCharAt(anon ? 'A' : '.', 2); }
     bool          GetAnonymous()         { return mHashKey.CharAt(2) == 'A'; }
 
-    bool          SupportsPipelining()   { return mSupportsPipelining; }
-    void          SetSupportsPipelining(bool support)
-                                         { mSupportsPipelining = support; }
-
     bool          ShouldForceConnectMethod();
     const nsCString &GetHost() { return mHost; }
 
+    bool          SupportsPipelining();
+    bool          SetSupportsPipelining(bool support);
+    void          BanPipelining();
+
 private:
     nsrefcnt               mRef;
     nsCString              mHashKey;
     nsCString              mHost;
     PRInt32                mPort;
     nsCOMPtr<nsProxyInfo>  mProxyInfo;
     bool                   mUsingHttpProxy;
     bool                   mUsingSSL;
     bool                   mSupportsPipelining;
+    bool                   mBannedPipelining;
 };
 
 #endif // nsHttpConnectionInfo_h__
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -185,16 +185,17 @@ nsHttpHandler::nsHttpHandler()
     , mMaxConnectionsPerServer(8)
     , mMaxPersistentConnectionsPerServer(2)
     , mMaxPersistentConnectionsPerProxy(4)
     , mMaxPipelinedRequests(2)
     , mRedirectionLimit(10)
     , mPhishyUserPassLength(1)
     , mQoSBits(0x00)
     , mPipeliningOverSSL(false)
+    , mEnforceAssocReq(false)
     , mInPrivateBrowsingMode(PRIVATE_BROWSING_UNKNOWN)
     , mLastUniqueID(NowInSeconds())
     , mSessionStartTime(0)
     , mLegacyAppName("Mozilla")
     , mLegacyAppVersion("5.0")
     , mProduct("Gecko")
     , mUserAgentIsDirty(true)
     , mUseCache(true)
@@ -1098,16 +1099,23 @@ nsHttpHandler::PrefsChanged(nsIPrefBranc
 
     if (PREF_CHANGED(HTTP_PREF("prompt-temp-redirect"))) {
         rv = prefs->GetBoolPref(HTTP_PREF("prompt-temp-redirect"), &cVar);
         if (NS_SUCCEEDED(rv)) {
             mPromptTempRedirect = cVar;
         }
     }
 
+    if (PREF_CHANGED(HTTP_PREF("assoc-req.enforce"))) {
+        cVar = false;
+        rv = prefs->GetBoolPref(HTTP_PREF("assoc-req.enforce"), &cVar);
+        if (NS_SUCCEEDED(rv))
+            mEnforceAssocReq = cVar;
+    }
+
     // enable Persistent caching for HTTPS - bug#205921    
     if (PREF_CHANGED(BROWSER_PREF("disk_cache_ssl"))) {
         cVar = false;
         rv = prefs->GetBoolPref(BROWSER_PREF("disk_cache_ssl"), &cVar);
         if (NS_SUCCEEDED(rv))
             mEnablePersistentHttpsCaching = cVar;
     }
 
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -105,16 +105,17 @@ public:
     PRUint16       MaxRequestAttempts()      { return mMaxRequestAttempts; }
     const char    *DefaultSocketType()       { return mDefaultSocketType.get(); /* ok to return null */ }
     nsIIDNService *IDNConverter()            { return mIDNConverter; }
     PRUint32       PhishyUserPassLength()    { return mPhishyUserPassLength; }
     PRUint8        GetQoSBits()              { return mQoSBits; }
     PRUint16       GetIdleSynTimeout()       { return mIdleSynTimeout; }
     bool           FastFallbackToIPv4()      { return mFastFallbackToIPv4; }
     PRUint32       MaxSocketCount();
+    bool           EnforceAssocReq()         { return mEnforceAssocReq; }
 
     bool           IsPersistentHttpsCachingEnabled() { return mEnablePersistentHttpsCaching; }
     bool           IsTelemetryEnabled() { return mTelemetryEnabled; }
     bool           AllowExperiments() { return mTelemetryEnabled && mAllowExperiments; }
 
     bool           IsSpdyEnabled() { return mEnableSpdy; }
     bool           CoalesceSpdy() { return mCoalesceSpdy; }
     bool           UseAlternateProtocol() { return mUseAlternateProtocol; }
@@ -295,16 +296,17 @@ private:
     // unless its length is less than this threshold.  this warning is
     // intended to protect the user against spoofing attempts that use
     // the userpass field of the URL to obscure the actual origin server.
     PRUint8  mPhishyUserPassLength;
 
     PRUint8  mQoSBits;
 
     bool mPipeliningOverSSL;
+    bool mEnforceAssocReq;
 
     // cached value of whether or not the browser is in private browsing mode.
     enum {
         PRIVATE_BROWSING_OFF = false,
         PRIVATE_BROWSING_ON = true,
         PRIVATE_BROWSING_UNKNOWN = 2
     } mInPrivateBrowsingMode;
 
--- a/netwerk/protocol/http/nsHttpPipeline.cpp
+++ b/netwerk/protocol/http/nsHttpPipeline.cpp
@@ -126,21 +126,30 @@ nsHttpPipeline::AddTransaction(nsAHttpTr
 {
     LOG(("nsHttpPipeline::AddTransaction [this=%x trans=%x]\n", this, trans));
 
     if (mRequestQ.Length() || mResponseQ.Length())
         mUtilizedPipeline = true;
 
     NS_ADDREF(trans);
     mRequestQ.AppendElement(trans);
+    PRInt32 qlen = mRequestQ.Length();
+    
+    if (qlen != 1) {
+        trans->SetPipelinePosition(qlen);
+    }
+    else {
+        // do it for this case in case an idempotent cancellation
+        // is being repeated and an old value needs to be cleared
+        trans->SetPipelinePosition(0);
+    }
 
     if (mConnection && !mClosed) {
         trans->SetConnection(this);
-
-        if (mRequestQ.Length() == 1)
+        if (qlen == 1)
             mConnection->ResumeSend();
     }
 
     return NS_OK;
 }
 
 PRUint16
 nsHttpPipeline::PipelineDepthAvailable()
@@ -158,16 +167,34 @@ nsHttpPipeline::PipelineDepthAvailable()
         trans = Response(0);
     if (trans && !(trans->Caps() & NS_HTTP_ALLOW_PIPELINING))
         return 0;
 
     // There is still some room available.
     return mMaxPipelineDepth - currentTransactions;
 }
 
+nsresult
+nsHttpPipeline::SetPipelinePosition(PRInt32 position)
+{
+    nsAHttpTransaction *trans = Response(0);
+    if (trans)
+        return trans->SetPipelinePosition(position);
+    return NS_OK;
+}
+
+PRInt32
+nsHttpPipeline::PipelinePosition()
+{
+    nsAHttpTransaction *trans = Response(0);
+    if (trans)
+        return trans->PipelinePosition();
+    return 2;
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpPipeline::nsISupports
 //-----------------------------------------------------------------------------
 
 NS_IMPL_THREADSAFE_ADDREF(nsHttpPipeline)
 NS_IMPL_THREADSAFE_RELEASE(nsHttpPipeline)
 
 // multiple inheritance fun :-)
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -114,16 +114,17 @@ nsHttpTransaction::nsHttpTransaction()
     , mContentLength(-1)
     , mContentRead(0)
     , mInvalidResponseBytesRead(0)
     , mChunkedDecoder(nsnull)
     , mStatus(NS_OK)
     , mPriority(0)
     , mRestartCount(0)
     , mCaps(0)
+    , mPipelinePosition(0)
     , mClosed(false)
     , mConnected(false)
     , mHaveStatusLine(false)
     , mHaveAllHeaders(false)
     , mTransactionDone(false)
     , mResponseIsComplete(false)
     , mDidContentStart(false)
     , mNoContent(false)
@@ -717,16 +718,29 @@ nsHttpTransaction::AddTransaction(nsAHtt
 }
 
 PRUint16
 nsHttpTransaction::PipelineDepthAvailable()
 {
     return 0;
 }
 
+nsresult
+nsHttpTransaction::SetPipelinePosition(PRInt32 position)
+{
+    mPipelinePosition = position;
+    return NS_OK;
+}
+ 
+PRInt32
+nsHttpTransaction::PipelinePosition()
+{
+    return mPipelinePosition;
+}
+
 //-----------------------------------------------------------------------------
 // nsHttpTransaction <private>
 //-----------------------------------------------------------------------------
 
 nsresult
 nsHttpTransaction::Restart()
 {
     NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -194,16 +194,17 @@ private:
     TimingStruct                    mTimings;
 
     nsresult                        mStatus;
 
     PRInt16                         mPriority;
 
     PRUint16                        mRestartCount;        // the number of times this transaction has been restarted
     PRUint8                         mCaps;
+    PRInt32                         mPipelinePosition;
 
     // state flags, all logically boolean, but not packed together into a
     // bitfield so as to avoid bitfield-induced races.  See bug 560579.
     bool                            mClosed;
     bool                            mConnected;
     bool                            mHaveStatusLine;
     bool                            mHaveAllHeaders;
     bool                            mTransactionDone;
--- a/netwerk/test/unit/head_channels.js
+++ b/netwerk/test/unit/head_channels.js
@@ -21,29 +21,31 @@ function read_stream(stream, count) {
   return data.join('');
 }
 
 const CL_EXPECT_FAILURE = 0x1;
 const CL_EXPECT_GZIP = 0x2;
 const CL_EXPECT_3S_DELAY = 0x4;
 const CL_SUSPEND = 0x8;
 const CL_ALLOW_UNKNOWN_CL = 0x10;
+const CL_EXPECT_LATE_FAILURE = 0x20;
 
 const SUSPEND_DELAY = 3000;
 
 /**
  * A stream listener that calls a callback function with a specified
  * context and the received data when the channel is loaded.
  *
  * Signature of the closure:
  *   void closure(in nsIRequest request, in ACString data, in JSObject context);
  *
  * This listener makes sure that various parts of the channel API are
  * implemented correctly and that the channel's status is a success code
- * (you can pass CL_EXPECT_FAILURE as flags to allow a failure code)
+ * (you can pass CL_EXPECT_FAILURE or CL_EXPECT_LATE_FAILURE as flags
+ * to allow a failure code)
  *
  * Note that it also requires a valid content length on the channel and
  * is thus not fully generic.
  */
 function ChannelListener(closure, ctx, flags) {
   this._closure = closure;
   this._closurectx = ctx;
   this._flags = flags;
@@ -126,25 +128,25 @@ ChannelListener.prototype = {
   onStopRequest: function(request, context, status) {
     try {
       var success = Components.isSuccessCode(status);
       if (!this._got_onstartrequest && success)
         do_throw("onStopRequest without onStartRequest event!");
       if (this._got_onstoprequest)
         do_throw("Got second onStopRequest event!");
       this._got_onstoprequest = true;
-      if ((this._flags & CL_EXPECT_FAILURE) && success)
+      if ((this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) && success)
         do_throw("Should have failed to load URL (status is " + status.toString(16) + ")");
-      else if (!(this._flags & CL_EXPECT_FAILURE) && !success)
+      else if (!(this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) && !success)
         do_throw("Failed to load URL: " + status.toString(16));
       if (status != request.status)
         do_throw("request.status does not match status arg to onStopRequest!");
       if (request.isPending())
         do_throw("request reports itself as pending from onStopRequest!");
-      if (!(this._flags & CL_EXPECT_FAILURE) &&
+      if (!(this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) &&
           !(this._flags & CL_EXPECT_GZIP) &&
           this._contentLen != -1)
           do_check_eq(this._buffer.length, this._contentLen)
     } catch (ex) {
       do_throw("Error in onStopRequest: " + ex);
     }
     try {
       this._closure(request, this._buffer, this._closurectx);
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_assoc.js
@@ -0,0 +1,90 @@
+do_load_httpd_js();
+
+var httpserver = new nsHttpServer();
+var currentTestIndex = 0;
+var tests = [
+             // this is valid
+             {url: "/assoc/assoctest?valid",
+              responseheader: [ "Assoc-Req: GET http://localhost:4444/assoc/assoctest?valid", 
+                                "Pragma: X-Verify-Assoc-Req" ],
+              flags : 0},
+
+             // this is invalid because the method is wrong
+             {url: "/assoc/assoctest?invalid",
+              responseheader: [ "Assoc-Req: POST http://localhost:4444/assoc/assoctest?invalid",
+                                "Pragma: X-Verify-Assoc-Req" ],
+              flags : CL_EXPECT_LATE_FAILURE},
+             
+             // this is invalid because the url is wrong
+             {url: "/assoc/assoctest?notvalid",
+              responseheader: [ "Assoc-Req: GET http://localhost:4444/wrongpath/assoc/assoctest?notvalid",
+                                "Pragma: X-Verify-Assoc-Req" ],
+              flags : CL_EXPECT_LATE_FAILURE},
+
+             // this is invalid because the space between method and URL is missing
+             {url: "/assoc/assoctest?invalid2",
+              responseheader: [ "Assoc-Req: GEThttp://localhost:4444/assoc/assoctest?invalid2",
+                                "Pragma: X-Verify-Assoc-Req" ],
+              flags : CL_EXPECT_LATE_FAILURE},
+];
+
+var oldPrefVal;
+var domBranch;
+
+function setupChannel(url)
+{
+    var ios = Components.classes["@mozilla.org/network/io-service;1"].
+                         getService(Ci.nsIIOService);
+    var chan = ios.newChannel("http://localhost:4444" + url, "", null);
+    return chan;
+}
+
+function startIter()
+{
+    var channel = setupChannel(tests[currentTestIndex].url);
+    channel.asyncOpen(new ChannelListener(completeIter,
+                                          channel, tests[currentTestIndex].flags), null);
+}
+
+function completeIter(request, data, ctx)
+{
+    if (++currentTestIndex < tests.length ) {
+        startIter();
+    } else {
+        domBranch.setBoolPref("enforce", oldPrefVal);
+        httpserver.stop(do_test_finished);
+    }
+}
+
+function run_test()
+{
+    var prefService =
+        Components.classes["@mozilla.org/preferences-service;1"]
+        .getService(Components.interfaces.nsIPrefService);
+    domBranch = prefService.getBranch("network.http.assoc-req.");
+    oldPrefVal = domBranch.getBoolPref("enforce");
+    domBranch.setBoolPref("enforce", true);
+
+    httpserver.registerPathHandler("/assoc/assoctest", handler);
+    httpserver.start(4444);
+
+    startIter();
+    do_test_pending();
+}
+
+function handler(metadata, response)
+{
+    var body = "thequickbrownfox";
+    response.setHeader("Content-Type", "text/plain", false);
+
+    var header = tests[currentTestIndex].responseheader;
+    if (header != undefined) {
+        for (var i = 0; i < header.length; i++) {
+            var splitHdr = header[i].split(": ");
+            response.setHeader(splitHdr[0], splitHdr[1], false);
+        }
+    }
+    
+    response.setStatusLine(metadata.httpVersion, 200, "OK");
+    response.bodyOutputStream.write(body, body.length);
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 head = head_channels.js
 tail = 
 
 [test_307_redirect.js]
 [test_NetUtil.js]
 [test_URIs.js]
 [test_aboutblank.js]
+[test_assoc.js]
 [test_auth_proxy.js]
 [test_authentication.js]
 # Bug 675039: test hangs consistently on Android
 skip-if = os == "android"
 [test_authpromptwrapper.js]
 [test_bug203271.js]
 [test_bug248970_cache.js]
 [test_bug248970_cookie.js]